├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── nightly.yml │ └── test.yml ├── .gitignore ├── .nvmrc ├── .openzeppelin ├── .gitignore └── mainnet.json ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── LICENSE ├── README.md ├── contracts ├── MedianOracle.sol ├── Orchestrator.sol ├── UFragments.sol ├── UFragmentsPolicy.sol ├── WAMPL.sol ├── _external │ ├── ERC20Detailed.sol │ ├── IERC20.sol │ ├── Initializable.sol │ ├── Ownable.sol │ └── SafeMath.sol ├── interfaces │ ├── IAMPL.sol │ ├── IAmpleforth.sol │ └── IOrchestrator.sol ├── lib │ ├── SafeMathInt.sol │ ├── Select.sol │ └── UInt256Lib.sol └── mocks │ ├── ConstructorRebaseCallerContract.sol │ ├── GetMedianOracleDataCallerContract.sol │ ├── Mock.sol │ ├── MockDownstream.sol │ ├── MockOracle.sol │ ├── MockUFragments.sol │ ├── MockUFragmentsPolicy.sol │ ├── RebaseCallerContract.sol │ ├── SafeMathIntMock.sol │ ├── SelectMock.sol │ └── UInt256LibMock.sol ├── external-artifacts ├── MultiSigWallet.json └── UFragmentsPolicy_v1.2.1.json ├── hardhat.config.ts ├── package.json ├── scripts ├── deploy.ts ├── helpers.ts └── upgrade.ts ├── test ├── simulation │ ├── supply_precision.ts │ └── transfer_precision.ts ├── unit │ ├── MedianOracle.ts │ ├── Orchestrator.ts │ ├── SafeMathInt.ts │ ├── Select.ts │ ├── UFragments.ts │ ├── UFragmentsPolicy.ts │ ├── UFragments_erc20_permit.ts │ ├── UInt256Lib.ts │ ├── WAMPL.ts │ ├── uFragments_elastic_behavior.ts │ └── uFragments_erc20_behavior.ts └── utils │ ├── signatures.ts │ └── utils.ts ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | node-version: [20.x] 15 | os: [ubuntu-latest] 16 | 17 | steps: 18 | - name: Setup Repo 19 | uses: actions/checkout@v4 20 | 21 | - name: Uses node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - name: Install all workspaces 27 | run: yarn install --immutable 28 | 29 | - name: Seutp workspaces 30 | run: yarn compile 31 | 32 | - name: Lint 33 | run: yarn format && yarn lint 34 | 35 | - name: Test 36 | run: yarn coverage 37 | 38 | - name: spot-contracts report coverage 39 | uses: coverallsapp/github-action@v2.3.4 40 | with: 41 | github-token: ${{ secrets.GITHUB_TOKEN }} 42 | path-to-lcov: "./coverage/lcov.info" -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | matrix: 15 | node-version: [20.x] 16 | os: [ubuntu-latest] 17 | 18 | steps: 19 | - name: Setup Repo 20 | uses: actions/checkout@v4 21 | 22 | - name: Uses node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | 27 | - name: Install all workspaces 28 | run: yarn install --immutable 29 | 30 | - name: Seutp workspaces 31 | run: yarn compile 32 | 33 | - name: Lint 34 | run: yarn format && yarn lint 35 | 36 | - name: Test 37 | run: yarn test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated files 2 | build 3 | dist 4 | 5 | ### Emacs ## 6 | *~ 7 | \#*\# 8 | .\#* 9 | 10 | ### Vim ## 11 | *.swp 12 | 13 | # 14 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 15 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 16 | # 17 | 18 | # User-specific stuff: 19 | .idea/**/workspace.xml 20 | .idea/**/tasks.xml 21 | .idea/dictionaries 22 | 23 | # Sensitive or high-churn files: 24 | .idea/**/dataSources/ 25 | .idea/**/dataSources.ids 26 | .idea/**/dataSources.local.xml 27 | .idea/**/sqlDataSources.xml 28 | .idea/**/dynamic.xml 29 | .idea/**/uiDesigner.xml 30 | 31 | # Gradle: 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # CMake 36 | cmake-build-debug/ 37 | cmake-build-release/ 38 | 39 | # Mongo Explorer plugin: 40 | .idea/**/mongoSettings.xml 41 | 42 | ## File-based project format: 43 | *.iws 44 | 45 | ## Plugin-specific files: 46 | 47 | # IntelliJ 48 | out/ 49 | 50 | # mpeltonen/sbt-idea plugin 51 | .idea_modules/ 52 | 53 | # JIRA plugin 54 | atlassian-ide-plugin.xml 55 | 56 | # Cursive Clojure plugin 57 | .idea/replstate.xml 58 | 59 | # Crashlytics plugin (for Android Studio and IntelliJ) 60 | com_crashlytics_export_strings.xml 61 | crashlytics.properties 62 | crashlytics-build.properties 63 | fabric.properties 64 | 65 | # NodeJS dependencies 66 | node_modules/* 67 | 68 | # ES-Lint 69 | .eslintcache 70 | 71 | # Solidity-Coverage 72 | allFiredEvents 73 | scTopics 74 | scDebugLog 75 | coverage.json 76 | coverage/ 77 | coverageEnv/ 78 | 79 | node_modules 80 | 81 | #Buidler files 82 | cache 83 | artifacts 84 | 85 | # env 86 | .env 87 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 -------------------------------------------------------------------------------- /.openzeppelin/.gitignore: -------------------------------------------------------------------------------- 1 | unknown-*.json -------------------------------------------------------------------------------- /.openzeppelin/mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "3.2", 3 | "admin": { 4 | "address": "0x6c8a3A2CC9b9831d550433626b262fB9365b5f63" 5 | }, 6 | "proxies": [ 7 | { 8 | "address": "0x1B228a749077b8e307C5856cE62Ef35d96Dca2ea", 9 | "kind": "transparent" 10 | } 11 | ], 12 | "impls": { 13 | "d8137c437d1dafc6dfc1b99b02500b756f9493029b926adccf16480f7a8e55eb": { 14 | "address": "0xE345465b36CC55275250f30bd32127d3bfe45d53", 15 | "layout": { 16 | "solcVersion": "0.8.4", 17 | "storage": [ 18 | { 19 | "label": "initialized", 20 | "offset": 0, 21 | "slot": "0", 22 | "type": "t_bool", 23 | "contract": "Initializable", 24 | "src": "contracts/_external/Initializable.sol:19" 25 | }, 26 | { 27 | "label": "initializing", 28 | "offset": 1, 29 | "slot": "0", 30 | "type": "t_bool", 31 | "contract": "Initializable", 32 | "src": "contracts/_external/Initializable.sol:24" 33 | }, 34 | { 35 | "label": "______gap", 36 | "offset": 0, 37 | "slot": "1", 38 | "type": "t_array(t_uint256)50_storage", 39 | "contract": "Initializable", 40 | "src": "contracts/_external/Initializable.sol:69" 41 | }, 42 | { 43 | "label": "_owner", 44 | "offset": 0, 45 | "slot": "51", 46 | "type": "t_address", 47 | "contract": "Ownable", 48 | "src": "contracts/_external/Ownable.sol:11" 49 | }, 50 | { 51 | "label": "______gap", 52 | "offset": 0, 53 | "slot": "52", 54 | "type": "t_array(t_uint256)50_storage", 55 | "contract": "Ownable", 56 | "src": "contracts/_external/Ownable.sol:75" 57 | }, 58 | { 59 | "label": "uFrags", 60 | "offset": 0, 61 | "slot": "102", 62 | "type": "t_contract(IUFragments)4306", 63 | "contract": "UFragmentsPolicy", 64 | "src": "contracts/UFragmentsPolicy.sol:40" 65 | }, 66 | { 67 | "label": "cpiOracle", 68 | "offset": 0, 69 | "slot": "103", 70 | "type": "t_contract(IOracle)4314", 71 | "contract": "UFragmentsPolicy", 72 | "src": "contracts/UFragmentsPolicy.sol:43" 73 | }, 74 | { 75 | "label": "marketOracle", 76 | "offset": 0, 77 | "slot": "104", 78 | "type": "t_contract(IOracle)4314", 79 | "contract": "UFragmentsPolicy", 80 | "src": "contracts/UFragmentsPolicy.sol:47" 81 | }, 82 | { 83 | "label": "baseCpi", 84 | "offset": 0, 85 | "slot": "105", 86 | "type": "t_uint256", 87 | "contract": "UFragmentsPolicy", 88 | "src": "contracts/UFragmentsPolicy.sol:50" 89 | }, 90 | { 91 | "label": "deviationThreshold", 92 | "offset": 0, 93 | "slot": "106", 94 | "type": "t_uint256", 95 | "contract": "UFragmentsPolicy", 96 | "src": "contracts/UFragmentsPolicy.sol:56" 97 | }, 98 | { 99 | "label": "rebaseLagDeprecated", 100 | "offset": 0, 101 | "slot": "107", 102 | "type": "t_uint256", 103 | "contract": "UFragmentsPolicy", 104 | "src": "contracts/UFragmentsPolicy.sol:58" 105 | }, 106 | { 107 | "label": "minRebaseTimeIntervalSec", 108 | "offset": 0, 109 | "slot": "108", 110 | "type": "t_uint256", 111 | "contract": "UFragmentsPolicy", 112 | "src": "contracts/UFragmentsPolicy.sol:61" 113 | }, 114 | { 115 | "label": "lastRebaseTimestampSec", 116 | "offset": 0, 117 | "slot": "109", 118 | "type": "t_uint256", 119 | "contract": "UFragmentsPolicy", 120 | "src": "contracts/UFragmentsPolicy.sol:64" 121 | }, 122 | { 123 | "label": "rebaseWindowOffsetSec", 124 | "offset": 0, 125 | "slot": "110", 126 | "type": "t_uint256", 127 | "contract": "UFragmentsPolicy", 128 | "src": "contracts/UFragmentsPolicy.sol:68" 129 | }, 130 | { 131 | "label": "rebaseWindowLengthSec", 132 | "offset": 0, 133 | "slot": "111", 134 | "type": "t_uint256", 135 | "contract": "UFragmentsPolicy", 136 | "src": "contracts/UFragmentsPolicy.sol:71" 137 | }, 138 | { 139 | "label": "epoch", 140 | "offset": 0, 141 | "slot": "112", 142 | "type": "t_uint256", 143 | "contract": "UFragmentsPolicy", 144 | "src": "contracts/UFragmentsPolicy.sol:74" 145 | }, 146 | { 147 | "label": "orchestrator", 148 | "offset": 0, 149 | "slot": "113", 150 | "type": "t_address", 151 | "contract": "UFragmentsPolicy", 152 | "src": "contracts/UFragmentsPolicy.sol:85" 153 | }, 154 | { 155 | "label": "rebaseFunctionLowerPercentage", 156 | "offset": 0, 157 | "slot": "114", 158 | "type": "t_int256", 159 | "contract": "UFragmentsPolicy", 160 | "src": "contracts/UFragmentsPolicy.sol:89" 161 | }, 162 | { 163 | "label": "rebaseFunctionUpperPercentage", 164 | "offset": 0, 165 | "slot": "115", 166 | "type": "t_int256", 167 | "contract": "UFragmentsPolicy", 168 | "src": "contracts/UFragmentsPolicy.sol:90" 169 | }, 170 | { 171 | "label": "rebaseFunctionGrowth", 172 | "offset": 0, 173 | "slot": "116", 174 | "type": "t_int256", 175 | "contract": "UFragmentsPolicy", 176 | "src": "contracts/UFragmentsPolicy.sol:91" 177 | } 178 | ], 179 | "types": { 180 | "t_address": { 181 | "label": "address", 182 | "numberOfBytes": "20" 183 | }, 184 | "t_array(t_uint256)50_storage": { 185 | "label": "uint256[50]", 186 | "numberOfBytes": "1600" 187 | }, 188 | "t_bool": { 189 | "label": "bool", 190 | "numberOfBytes": "1" 191 | }, 192 | "t_contract(IOracle)4314": { 193 | "label": "contract IOracle", 194 | "numberOfBytes": "20" 195 | }, 196 | "t_contract(IUFragments)4306": { 197 | "label": "contract IUFragments", 198 | "numberOfBytes": "20" 199 | }, 200 | "t_int256": { 201 | "label": "int256", 202 | "numberOfBytes": "32" 203 | }, 204 | "t_uint256": { 205 | "label": "uint256", 206 | "numberOfBytes": "32" 207 | } 208 | } 209 | } 210 | }, 211 | "48a1e26da957363514647440ab4b33a0cf48bd6fa95acee2a194faceb2076760": { 212 | "address": "0x2C628463de226433820DD479Be27360Deb4592d1", 213 | "txHash": "0x2a444748f3c63ad130a3cd16258c709d0abf0508bc807c2396239299c1fe4f0f", 214 | "layout": { 215 | "solcVersion": "0.8.4", 216 | "storage": [ 217 | { 218 | "label": "initialized", 219 | "offset": 0, 220 | "slot": "0", 221 | "type": "t_bool", 222 | "contract": "Initializable", 223 | "src": "contracts/_external/Initializable.sol:19" 224 | }, 225 | { 226 | "label": "initializing", 227 | "offset": 1, 228 | "slot": "0", 229 | "type": "t_bool", 230 | "contract": "Initializable", 231 | "src": "contracts/_external/Initializable.sol:24" 232 | }, 233 | { 234 | "label": "______gap", 235 | "offset": 0, 236 | "slot": "1", 237 | "type": "t_array(t_uint256)50_storage", 238 | "contract": "Initializable", 239 | "src": "contracts/_external/Initializable.sol:69" 240 | }, 241 | { 242 | "label": "_owner", 243 | "offset": 0, 244 | "slot": "51", 245 | "type": "t_address", 246 | "contract": "Ownable", 247 | "src": "contracts/_external/Ownable.sol:11" 248 | }, 249 | { 250 | "label": "______gap", 251 | "offset": 0, 252 | "slot": "52", 253 | "type": "t_array(t_uint256)50_storage", 254 | "contract": "Ownable", 255 | "src": "contracts/_external/Ownable.sol:75" 256 | }, 257 | { 258 | "label": "uFrags", 259 | "offset": 0, 260 | "slot": "102", 261 | "type": "t_contract(IUFragments)20", 262 | "contract": "UFragmentsPolicy", 263 | "src": "contracts/UFragmentsPolicy.sol:48" 264 | }, 265 | { 266 | "label": "cpiOracle", 267 | "offset": 0, 268 | "slot": "103", 269 | "type": "t_contract(IOracle)28", 270 | "contract": "UFragmentsPolicy", 271 | "src": "contracts/UFragmentsPolicy.sol:51" 272 | }, 273 | { 274 | "label": "marketOracle", 275 | "offset": 0, 276 | "slot": "104", 277 | "type": "t_contract(IOracle)28", 278 | "contract": "UFragmentsPolicy", 279 | "src": "contracts/UFragmentsPolicy.sol:55" 280 | }, 281 | { 282 | "label": "baseCpi_DEPRECATED", 283 | "offset": 0, 284 | "slot": "105", 285 | "type": "t_uint256", 286 | "contract": "UFragmentsPolicy", 287 | "src": "contracts/UFragmentsPolicy.sol:58", 288 | "renamedFrom": "baseCpi" 289 | }, 290 | { 291 | "label": "deviationThreshold", 292 | "offset": 0, 293 | "slot": "106", 294 | "type": "t_uint256", 295 | "contract": "UFragmentsPolicy", 296 | "src": "contracts/UFragmentsPolicy.sol:64" 297 | }, 298 | { 299 | "label": "rebaseLagDeprecated", 300 | "offset": 0, 301 | "slot": "107", 302 | "type": "t_uint256", 303 | "contract": "UFragmentsPolicy", 304 | "src": "contracts/UFragmentsPolicy.sol:66" 305 | }, 306 | { 307 | "label": "minRebaseTimeIntervalSec", 308 | "offset": 0, 309 | "slot": "108", 310 | "type": "t_uint256", 311 | "contract": "UFragmentsPolicy", 312 | "src": "contracts/UFragmentsPolicy.sol:69" 313 | }, 314 | { 315 | "label": "lastRebaseTimestampSec", 316 | "offset": 0, 317 | "slot": "109", 318 | "type": "t_uint256", 319 | "contract": "UFragmentsPolicy", 320 | "src": "contracts/UFragmentsPolicy.sol:72" 321 | }, 322 | { 323 | "label": "rebaseWindowOffsetSec", 324 | "offset": 0, 325 | "slot": "110", 326 | "type": "t_uint256", 327 | "contract": "UFragmentsPolicy", 328 | "src": "contracts/UFragmentsPolicy.sol:76" 329 | }, 330 | { 331 | "label": "rebaseWindowLengthSec", 332 | "offset": 0, 333 | "slot": "111", 334 | "type": "t_uint256", 335 | "contract": "UFragmentsPolicy", 336 | "src": "contracts/UFragmentsPolicy.sol:79" 337 | }, 338 | { 339 | "label": "epoch", 340 | "offset": 0, 341 | "slot": "112", 342 | "type": "t_uint256", 343 | "contract": "UFragmentsPolicy", 344 | "src": "contracts/UFragmentsPolicy.sol:82" 345 | }, 346 | { 347 | "label": "orchestrator", 348 | "offset": 0, 349 | "slot": "113", 350 | "type": "t_address", 351 | "contract": "UFragmentsPolicy", 352 | "src": "contracts/UFragmentsPolicy.sol:93" 353 | }, 354 | { 355 | "label": "rebaseFunctionLowerPercentage", 356 | "offset": 0, 357 | "slot": "114", 358 | "type": "t_int256", 359 | "contract": "UFragmentsPolicy", 360 | "src": "contracts/UFragmentsPolicy.sol:97" 361 | }, 362 | { 363 | "label": "rebaseFunctionUpperPercentage", 364 | "offset": 0, 365 | "slot": "115", 366 | "type": "t_int256", 367 | "contract": "UFragmentsPolicy", 368 | "src": "contracts/UFragmentsPolicy.sol:98" 369 | }, 370 | { 371 | "label": "rebaseFunctionGrowth", 372 | "offset": 0, 373 | "slot": "116", 374 | "type": "t_int256", 375 | "contract": "UFragmentsPolicy", 376 | "src": "contracts/UFragmentsPolicy.sol:99" 377 | } 378 | ], 379 | "types": { 380 | "t_address": { 381 | "label": "address", 382 | "numberOfBytes": "20" 383 | }, 384 | "t_array(t_uint256)50_storage": { 385 | "label": "uint256[50]", 386 | "numberOfBytes": "1600" 387 | }, 388 | "t_bool": { 389 | "label": "bool", 390 | "numberOfBytes": "1" 391 | }, 392 | "t_contract(IOracle)28": { 393 | "label": "contract IOracle", 394 | "numberOfBytes": "20" 395 | }, 396 | "t_contract(IUFragments)20": { 397 | "label": "contract IUFragments", 398 | "numberOfBytes": "20" 399 | }, 400 | "t_int256": { 401 | "label": "int256", 402 | "numberOfBytes": "32" 403 | }, 404 | "t_uint256": { 405 | "label": "uint256", 406 | "numberOfBytes": "32" 407 | } 408 | } 409 | } 410 | }, 411 | "c7369a7d61d189dc4ac496f58ed60273c5ce42a3d8df698d7398067ab9e28815": { 412 | "address": "0x671f768157f6C8A33aDA9b864C8DfaF5b13f1e9F", 413 | "txHash": "0x867d84cd156cf4afee08a44ed6c96cbddbd3c75269e50f8c015edda7628c713f", 414 | "layout": { 415 | "solcVersion": "0.8.4", 416 | "storage": [ 417 | { 418 | "label": "initialized", 419 | "offset": 0, 420 | "slot": "0", 421 | "type": "t_bool", 422 | "contract": "Initializable", 423 | "src": "contracts/_external/Initializable.sol:19" 424 | }, 425 | { 426 | "label": "initializing", 427 | "offset": 1, 428 | "slot": "0", 429 | "type": "t_bool", 430 | "contract": "Initializable", 431 | "src": "contracts/_external/Initializable.sol:24" 432 | }, 433 | { 434 | "label": "______gap", 435 | "offset": 0, 436 | "slot": "1", 437 | "type": "t_array(t_uint256)50_storage", 438 | "contract": "Initializable", 439 | "src": "contracts/_external/Initializable.sol:69" 440 | }, 441 | { 442 | "label": "_owner", 443 | "offset": 0, 444 | "slot": "51", 445 | "type": "t_address", 446 | "contract": "Ownable", 447 | "src": "contracts/_external/Ownable.sol:11" 448 | }, 449 | { 450 | "label": "______gap", 451 | "offset": 0, 452 | "slot": "52", 453 | "type": "t_array(t_uint256)50_storage", 454 | "contract": "Ownable", 455 | "src": "contracts/_external/Ownable.sol:75" 456 | }, 457 | { 458 | "label": "uFrags", 459 | "offset": 0, 460 | "slot": "102", 461 | "type": "t_contract(IUFragments)20", 462 | "contract": "UFragmentsPolicy", 463 | "src": "contracts/UFragmentsPolicy.sol:48" 464 | }, 465 | { 466 | "label": "cpiOracle", 467 | "offset": 0, 468 | "slot": "103", 469 | "type": "t_contract(IOracle)28", 470 | "contract": "UFragmentsPolicy", 471 | "src": "contracts/UFragmentsPolicy.sol:51" 472 | }, 473 | { 474 | "label": "marketOracle", 475 | "offset": 0, 476 | "slot": "104", 477 | "type": "t_contract(IOracle)28", 478 | "contract": "UFragmentsPolicy", 479 | "src": "contracts/UFragmentsPolicy.sol:55" 480 | }, 481 | { 482 | "label": "baseCpi_DEPRECATED", 483 | "offset": 0, 484 | "slot": "105", 485 | "type": "t_uint256", 486 | "contract": "UFragmentsPolicy", 487 | "src": "contracts/UFragmentsPolicy.sol:58", 488 | "renamedFrom": "baseCpi" 489 | }, 490 | { 491 | "label": "deviationThreshold", 492 | "offset": 0, 493 | "slot": "106", 494 | "type": "t_uint256", 495 | "contract": "UFragmentsPolicy", 496 | "src": "contracts/UFragmentsPolicy.sol:64" 497 | }, 498 | { 499 | "label": "rebaseLagDeprecated", 500 | "offset": 0, 501 | "slot": "107", 502 | "type": "t_uint256", 503 | "contract": "UFragmentsPolicy", 504 | "src": "contracts/UFragmentsPolicy.sol:66" 505 | }, 506 | { 507 | "label": "minRebaseTimeIntervalSec", 508 | "offset": 0, 509 | "slot": "108", 510 | "type": "t_uint256", 511 | "contract": "UFragmentsPolicy", 512 | "src": "contracts/UFragmentsPolicy.sol:69" 513 | }, 514 | { 515 | "label": "lastRebaseTimestampSec", 516 | "offset": 0, 517 | "slot": "109", 518 | "type": "t_uint256", 519 | "contract": "UFragmentsPolicy", 520 | "src": "contracts/UFragmentsPolicy.sol:72" 521 | }, 522 | { 523 | "label": "rebaseWindowOffsetSec", 524 | "offset": 0, 525 | "slot": "110", 526 | "type": "t_uint256", 527 | "contract": "UFragmentsPolicy", 528 | "src": "contracts/UFragmentsPolicy.sol:76" 529 | }, 530 | { 531 | "label": "rebaseWindowLengthSec", 532 | "offset": 0, 533 | "slot": "111", 534 | "type": "t_uint256", 535 | "contract": "UFragmentsPolicy", 536 | "src": "contracts/UFragmentsPolicy.sol:79" 537 | }, 538 | { 539 | "label": "epoch", 540 | "offset": 0, 541 | "slot": "112", 542 | "type": "t_uint256", 543 | "contract": "UFragmentsPolicy", 544 | "src": "contracts/UFragmentsPolicy.sol:82" 545 | }, 546 | { 547 | "label": "orchestrator", 548 | "offset": 0, 549 | "slot": "113", 550 | "type": "t_address", 551 | "contract": "UFragmentsPolicy", 552 | "src": "contracts/UFragmentsPolicy.sol:93" 553 | }, 554 | { 555 | "label": "rebaseFunctionLowerPercentage", 556 | "offset": 0, 557 | "slot": "114", 558 | "type": "t_int256", 559 | "contract": "UFragmentsPolicy", 560 | "src": "contracts/UFragmentsPolicy.sol:97" 561 | }, 562 | { 563 | "label": "rebaseFunctionUpperPercentage", 564 | "offset": 0, 565 | "slot": "115", 566 | "type": "t_int256", 567 | "contract": "UFragmentsPolicy", 568 | "src": "contracts/UFragmentsPolicy.sol:98" 569 | }, 570 | { 571 | "label": "rebaseFunctionGrowth", 572 | "offset": 0, 573 | "slot": "116", 574 | "type": "t_int256", 575 | "contract": "UFragmentsPolicy", 576 | "src": "contracts/UFragmentsPolicy.sol:99" 577 | } 578 | ], 579 | "types": { 580 | "t_address": { 581 | "label": "address", 582 | "numberOfBytes": "20" 583 | }, 584 | "t_array(t_uint256)50_storage": { 585 | "label": "uint256[50]", 586 | "numberOfBytes": "1600" 587 | }, 588 | "t_bool": { 589 | "label": "bool", 590 | "numberOfBytes": "1" 591 | }, 592 | "t_contract(IOracle)28": { 593 | "label": "contract IOracle", 594 | "numberOfBytes": "20" 595 | }, 596 | "t_contract(IUFragments)20": { 597 | "label": "contract IUFragments", 598 | "numberOfBytes": "20" 599 | }, 600 | "t_int256": { 601 | "label": "int256", 602 | "numberOfBytes": "32" 603 | }, 604 | "t_uint256": { 605 | "label": "uint256", 606 | "numberOfBytes": "32" 607 | } 608 | } 609 | } 610 | } 611 | } 612 | } 613 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "bracketSpacing": true, 6 | "printWidth": 80, 7 | "overrides": [ 8 | { 9 | "files": "*.sol", 10 | "options": { 11 | "printWidth": 100, 12 | "tabWidth": 4, 13 | "useTabs": false, 14 | "singleQuote": false, 15 | "bracketSpacing": false, 16 | "explicitTypes": "always" 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['mocks'], 3 | } 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "not-rely-on-time": "off", 7 | "max-line-length": ["error", 105] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ampleforth 2 | 3 | [![Nightly](https://github.com/ampleforth/ampleforth-contracts/actions/workflows/nightly.yml/badge.svg)](https://github.com/ampleforth/ampleforth-contracts/actions/workflows/nightly.yml)  [![Coverage Status](https://coveralls.io/repos/github/ampleforth/ampleforth-contracts/badge.svg?branch=master)](https://coveralls.io/github/ampleforth/ampleforth-contracts?branch=master) 4 | 5 | Ampleforth (code name uFragments) is a decentralized elastic supply protocol. It maintains a stable unit price by adjusting supply directly to and from wallet holders. You can read the [whitepaper](https://drive.google.com/file/d/1I-NmSnQ6E7wY1nyouuf-GuDdJWNCnJWl/view) for the motivation and a complete description of the protocol. 6 | 7 | This repository is a collection of [smart contracts](http://ampleforth.org/docs) that implement the Ampleforth protocol on the Ethereum blockchain. 8 | 9 | The official mainnet addresses are: 10 | 11 | - ERC-20 Token: [0xD46bA6D942050d489DBd938a2C909A5d5039A161](https://etherscan.io/token/0xd46ba6d942050d489dbd938a2c909a5d5039a161) 12 | - Supply Policy: [0x1B228a749077b8e307C5856cE62Ef35d96Dca2ea](https://etherscan.io/address/0x1b228a749077b8e307c5856ce62ef35d96dca2ea) 13 | - Orchestrator: [0x6fb00a180781e75f87e2b690af0196baa77c7e7c](https://etherscan.io/address/0x6fb00a180781e75f87e2b690af0196baa77c7e7c) 14 | - Market Oracle: [0x99c9775e076fdf99388c029550155032ba2d8914](https://etherscan.io/address/0x99c9775e076fdf99388c029550155032ba2d8914) 15 | - CPI Oracle: [0x2A18bfb505b49AED12F19F271cC1183F98ff4f71](https://etherscan.io/address/0x2A18bfb505b49AED12F19F271cC1183F98ff4f71) 16 | - WAMPL: [0xEDB171C18cE90B633DB442f2A6F72874093b49Ef](https://etherscan.io/address/0xEDB171C18cE90B633DB442f2A6F72874093b49Ef) 17 | 18 | ## Table of Contents 19 | 20 | - [Install](#install) 21 | - [Testing](#testing) 22 | - [Testnets](#testnets) 23 | - [Contribute](#contribute) 24 | - [License](#license) 25 | 26 | ## Install 27 | 28 | ```bash 29 | # Install project dependencies 30 | yarn 31 | ``` 32 | 33 | ## Testing 34 | 35 | ```bash 36 | # Run all unit tests (compatible with node v12+) 37 | yarn test 38 | ``` 39 | 40 | ## Testnets 41 | 42 | There is a testnet deployment on Goerli. It rebases hourly using real market data. 43 | 44 | - ERC-20 Token: [0x08c5b39F000705ebeC8427C1d64D6262392944EE](https://goerli.etherscan.io/token/0x08c5b39F000705ebeC8427C1d64D6262392944EE) 45 | - Supply Policy: [0x047b82a5D79d9DF62dE4f34CbaBa83F71848a6BF](https://goerli.etherscan.io/address/0x047b82a5D79d9DF62dE4f34CbaBa83F71848a6BF) 46 | - Orchestrator: [0x0ec93391752ef1A06AA2b83D15c3a5814651C891](https://goerli.etherscan.io/address/0x0ec93391752ef1A06AA2b83D15c3a5814651C891) 47 | - Market Oracle: [0xd4F96E4aC4B4f4E2359734a89b5484196298B69D](https://goerli.etherscan.io/address/0xd4F96E4aC4B4f4E2359734a89b5484196298B69D) 48 | - CPI Oracle: [0x53c75D13a07AA02615Cb43e942829862C963D9bf](https://goerli.etherscan.io/address/0x53c75D13a07AA02615Cb43e942829862C963D9bf) 49 | - Admin: [0x02C32fB5498e89a8750cc9Bd66382a681665c3a3](https://goerli.etherscan.io/address/0x02C32fB5498e89a8750cc9Bd66382a681665c3a3) 50 | - WAMPL: [0x3b624861a14979537DE1B88F9565F41a7fc45FBf](https://goerli.etherscan.io/address/0x3b624861a14979537DE1B88F9565F41a7fc45FBf) 51 | 52 | ## Contribute 53 | 54 | To report bugs within this package, create an issue in this repository. 55 | For security issues, please contact dev-support@ampleforth.org. 56 | When submitting code ensure that it is free of lint errors and has 100% test coverage. 57 | 58 | ```bash 59 | # Lint code 60 | yarn lint 61 | 62 | # Format code 63 | yarn format 64 | 65 | # Run solidity coverage report (compatible with node v12) 66 | yarn coverage 67 | 68 | # Run solidity gas usage report 69 | yarn profile 70 | ``` 71 | 72 | ## License 73 | 74 | [GNU General Public License v3.0 (c) 2018 Fragments, Inc.](./LICENSE) 75 | -------------------------------------------------------------------------------- /contracts/MedianOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.4; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import {MathUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; 6 | import "./lib/Select.sol"; 7 | 8 | interface IOracle { 9 | function getData() external returns (uint256, bool); 10 | } 11 | 12 | /** 13 | * @title Median Oracle 14 | * 15 | * @notice Provides a value onchain that's aggregated from a whitelisted set of 16 | * providers. 17 | */ 18 | contract MedianOracle is OwnableUpgradeable, IOracle { 19 | using MathUpgradeable for uint256; 20 | 21 | uint8 public constant DECIMALS = 18; 22 | 23 | struct Report { 24 | uint256 timestamp; 25 | uint256 payload; 26 | } 27 | 28 | // Addresses of providers authorized to push reports. 29 | address[] public providers; 30 | 31 | // Reports indexed by provider address. Report[0].timestamp > 0 32 | // indicates provider existence. 33 | mapping(address => Report[2]) public providerReports; 34 | 35 | event ProviderAdded(address provider); 36 | event ProviderRemoved(address provider); 37 | event ReportTimestampOutOfRange(address provider); 38 | event ProviderReportPushed(address indexed provider, uint256 payload, uint256 timestamp); 39 | 40 | // The number of seconds after which the report is deemed expired. 41 | uint256 public reportExpirationTimeSec; 42 | 43 | // The number of seconds since reporting that has to pass before a report 44 | // is usable. 45 | uint256 public reportDelaySec; 46 | 47 | // The minimum number of providers with valid reports to consider the 48 | // aggregate report valid. 49 | uint256 public minimumProviders = 1; 50 | 51 | // Timestamp of 1 is used to mark uninitialized and invalidated data. 52 | // This is needed so that timestamp of 1 is always considered expired. 53 | uint256 private constant MAX_REPORT_EXPIRATION_TIME = 520 weeks; 54 | 55 | // The final median value is scaled by multiplying by this factor. 56 | uint256 public scalar; 57 | 58 | /** 59 | * @notice Contract state initialization. 60 | * 61 | * @param reportExpirationTimeSec_ The number of seconds after which the 62 | * report is deemed expired. 63 | * @param reportDelaySec_ The number of seconds since reporting that has to 64 | * pass before a report is usable 65 | * @param minimumProviders_ The minimum number of providers with valid 66 | * reports to consider the aggregate report valid. 67 | * @param scalar_ The scaling factor applied on the result. 68 | */ 69 | function init( 70 | uint256 reportExpirationTimeSec_, 71 | uint256 reportDelaySec_, 72 | uint256 minimumProviders_, 73 | uint256 scalar_ 74 | ) public initializer { 75 | require(reportExpirationTimeSec_ <= MAX_REPORT_EXPIRATION_TIME); 76 | require(minimumProviders_ > 0); 77 | reportExpirationTimeSec = reportExpirationTimeSec_; 78 | reportDelaySec = reportDelaySec_; 79 | minimumProviders = minimumProviders_; 80 | scalar = scalar_; 81 | __Ownable_init(); 82 | } 83 | 84 | /** 85 | * @notice Sets the report expiration period. 86 | * @param reportExpirationTimeSec_ The number of seconds after which the 87 | * report is deemed expired. 88 | */ 89 | function setReportExpirationTimeSec(uint256 reportExpirationTimeSec_) external onlyOwner { 90 | require(reportExpirationTimeSec_ <= MAX_REPORT_EXPIRATION_TIME); 91 | reportExpirationTimeSec = reportExpirationTimeSec_; 92 | } 93 | 94 | /** 95 | * @notice Sets the time period since reporting that has to pass before a 96 | * report is usable. 97 | * @param reportDelaySec_ The new delay period in seconds. 98 | */ 99 | function setReportDelaySec(uint256 reportDelaySec_) external onlyOwner { 100 | reportDelaySec = reportDelaySec_; 101 | } 102 | 103 | /** 104 | * @notice Sets the minimum number of providers with valid reports to 105 | * consider the aggregate report valid. 106 | * @param minimumProviders_ The new minimum number of providers. 107 | */ 108 | function setMinimumProviders(uint256 minimumProviders_) external onlyOwner { 109 | require(minimumProviders_ > 0); 110 | minimumProviders = minimumProviders_; 111 | } 112 | 113 | /** 114 | * @notice Sets the scaling factor. 115 | * @param scalar_ The scaling factor. 116 | */ 117 | function setScalar(uint256 scalar_) external onlyOwner { 118 | scalar = scalar_; 119 | } 120 | 121 | /** 122 | * @notice Pushes a report for the calling provider. 123 | * @param payload is expected to be a fixed point number with `DECIMALS` decimals. 124 | */ 125 | function pushReport(uint256 payload) external { 126 | address providerAddress = msg.sender; 127 | Report[2] storage reports = providerReports[providerAddress]; 128 | uint256[2] memory timestamps = [reports[0].timestamp, reports[1].timestamp]; 129 | 130 | // Checks that this providerAddress is already whitelisted 131 | require(timestamps[0] > 0); 132 | 133 | uint8 index_recent = timestamps[0] >= timestamps[1] ? 0 : 1; 134 | uint8 index_past = 1 - index_recent; 135 | 136 | uint256 minValidTimestamp = block.timestamp - reportExpirationTimeSec; 137 | uint256 maxValidTimestamp = block.timestamp - reportDelaySec; 138 | 139 | // Check that the push is not too soon after the last one. 140 | // unless past one is already expired 141 | require( 142 | timestamps[index_past] < minValidTimestamp || 143 | timestamps[index_recent] <= maxValidTimestamp 144 | ); 145 | 146 | reports[index_past].timestamp = block.timestamp; 147 | reports[index_past].payload = payload; 148 | 149 | emit ProviderReportPushed(providerAddress, payload, block.timestamp); 150 | } 151 | 152 | /** 153 | * @notice Invalidates the reports of the calling provider. 154 | */ 155 | function purgeReports() external { 156 | address providerAddress = msg.sender; 157 | // Check that this providerAddress is already whitelisted 158 | require(providerReports[providerAddress][0].timestamp > 0); 159 | providerReports[providerAddress][0].timestamp = 1; 160 | providerReports[providerAddress][1].timestamp = 1; 161 | } 162 | 163 | /** 164 | * @notice Computes median of provider reports whose timestamps are in the 165 | * valid timestamp range. 166 | * @return AggregatedValue: Median of providers reported values. 167 | * valid: Boolean indicating an aggregated value was computed successfully. 168 | */ 169 | function getData() external override returns (uint256, bool) { 170 | uint256 reportsCount = providers.length; 171 | uint256[] memory validReports = new uint256[](reportsCount); 172 | uint256 size = 0; 173 | uint256 minValidTimestamp = block.timestamp - reportExpirationTimeSec; 174 | uint256 maxValidTimestamp = block.timestamp - reportDelaySec; 175 | 176 | for (uint256 i = 0; i < reportsCount; i++) { 177 | address providerAddress = providers[i]; 178 | Report[2] memory reports = providerReports[providerAddress]; 179 | 180 | uint8 index_recent = reports[0].timestamp >= reports[1].timestamp ? 0 : 1; 181 | uint8 index_past = 1 - index_recent; 182 | uint256 reportTimestampRecent = reports[index_recent].timestamp; 183 | if (reportTimestampRecent > maxValidTimestamp) { 184 | // Recent report is too recent. 185 | uint256 reportTimestampPast = providerReports[providerAddress][index_past] 186 | .timestamp; 187 | if (reportTimestampPast < minValidTimestamp) { 188 | // Past report is too old. 189 | // Deprecated: emit ReportTimestampOutOfRange(providerAddress); 190 | } else if (reportTimestampPast > maxValidTimestamp) { 191 | // Past report is too recent. 192 | // Deprecated: emit ReportTimestampOutOfRange(providerAddress); 193 | } else { 194 | // Using past report. 195 | validReports[size++] = providerReports[providerAddress][index_past].payload; 196 | } 197 | } else { 198 | // Recent report is not too recent. 199 | if (reportTimestampRecent < minValidTimestamp) { 200 | // Recent report is too old. 201 | // Deprecated: emit ReportTimestampOutOfRange(providerAddress); 202 | } else { 203 | // Using recent report. 204 | validReports[size++] = providerReports[providerAddress][index_recent].payload; 205 | } 206 | } 207 | } 208 | 209 | if (size < minimumProviders) { 210 | return (0, false); 211 | } 212 | 213 | uint256 result = Select.computeMedian(validReports, size); 214 | result = result.mulDiv(scalar, 10**DECIMALS); 215 | return (result, true); 216 | } 217 | 218 | /** 219 | * @notice Authorizes a provider. 220 | * @param provider Address of the provider. 221 | */ 222 | function addProvider(address provider) external onlyOwner { 223 | require(providerReports[provider][0].timestamp == 0); 224 | providers.push(provider); 225 | providerReports[provider][0].timestamp = 1; 226 | emit ProviderAdded(provider); 227 | } 228 | 229 | /** 230 | * @notice Revokes provider authorization. 231 | * @param provider Address of the provider. 232 | */ 233 | function removeProvider(address provider) external onlyOwner { 234 | delete providerReports[provider]; 235 | for (uint256 i = 0; i < providers.length; i++) { 236 | if (providers[i] == provider) { 237 | if (i + 1 != providers.length) { 238 | providers[i] = providers[providers.length - 1]; 239 | } 240 | providers.pop(); 241 | emit ProviderRemoved(provider); 242 | break; 243 | } 244 | } 245 | } 246 | 247 | /** 248 | * @return The number of authorized providers. 249 | */ 250 | function providersSize() external view returns (uint256) { 251 | return providers.length; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /contracts/Orchestrator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.4; 3 | 4 | import "./_external/Ownable.sol"; 5 | 6 | interface IUFragmentsPolicy { 7 | function rebase() external; 8 | } 9 | 10 | /** 11 | * @title Orchestrator 12 | * @notice The orchestrator is the main entry point for rebase operations. It coordinates the policy 13 | * actions with external consumers. 14 | */ 15 | contract Orchestrator is Ownable { 16 | struct Transaction { 17 | bool enabled; 18 | address destination; 19 | bytes data; 20 | } 21 | 22 | // Stable ordering is not guaranteed. 23 | Transaction[] public transactions; 24 | 25 | IUFragmentsPolicy public policy; 26 | 27 | /** 28 | * @param policy_ Address of the UFragments policy. 29 | */ 30 | constructor(address policy_) public { 31 | Ownable.initialize(msg.sender); 32 | policy = IUFragmentsPolicy(policy_); 33 | } 34 | 35 | /** 36 | * @notice Main entry point to initiate a rebase operation. 37 | * The Orchestrator calls rebase on the policy and notifies downstream applications. 38 | * Contracts are guarded from calling, to avoid flash loan attacks on liquidity 39 | * providers. 40 | * If a transaction in the transaction list fails, Orchestrator will stop execution 41 | * and revert to prevent a gas underprice attack. 42 | */ 43 | function rebase() external { 44 | require(msg.sender == tx.origin); // solhint-disable-line avoid-tx-origin 45 | 46 | policy.rebase(); 47 | 48 | for (uint256 i = 0; i < transactions.length; i++) { 49 | Transaction storage t = transactions[i]; 50 | if (t.enabled) { 51 | (bool result, ) = t.destination.call(t.data); 52 | if (!result) { 53 | revert("Transaction Failed"); 54 | } 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * @notice Adds a transaction that gets called for a downstream receiver of rebases 61 | * @param destination Address of contract destination 62 | * @param data Transaction data payload 63 | */ 64 | function addTransaction(address destination, bytes memory data) external onlyOwner { 65 | transactions.push(Transaction({enabled: true, destination: destination, data: data})); 66 | } 67 | 68 | /** 69 | * @param index Index of transaction to remove. 70 | * Transaction ordering may have changed since adding. 71 | */ 72 | function removeTransaction(uint256 index) external onlyOwner { 73 | require(index < transactions.length, "index out of bounds"); 74 | 75 | if (index < transactions.length - 1) { 76 | transactions[index] = transactions[transactions.length - 1]; 77 | } 78 | 79 | transactions.pop(); 80 | } 81 | 82 | /** 83 | * @param index Index of transaction. Transaction ordering may have changed since adding. 84 | * @param enabled True for enabled, false for disabled. 85 | */ 86 | function setTransactionEnabled(uint256 index, bool enabled) external onlyOwner { 87 | require(index < transactions.length, "index must be in range of stored tx list"); 88 | transactions[index].enabled = enabled; 89 | } 90 | 91 | /** 92 | * @return Number of transactions, both enabled and disabled, in transactions list. 93 | */ 94 | function transactionsSize() external view returns (uint256) { 95 | return transactions.length; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /contracts/UFragments.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.4; 3 | 4 | import "./_external/SafeMath.sol"; 5 | import "./_external/Ownable.sol"; 6 | import "./_external/ERC20Detailed.sol"; 7 | 8 | import "./lib/SafeMathInt.sol"; 9 | 10 | /** 11 | * @title uFragments ERC20 token 12 | * @dev This is part of an implementation of the uFragments Ideal Money protocol. 13 | * uFragments is a normal ERC20 token, but its supply can be adjusted by splitting and 14 | * combining tokens proportionally across all wallets. 15 | * 16 | * uFragment balances are internally represented with a hidden denomination, 'gons'. 17 | * We support splitting the currency in expansion and combining the currency on contraction by 18 | * changing the exchange rate between the hidden 'gons' and the public 'fragments'. 19 | */ 20 | contract UFragments is ERC20Detailed, Ownable { 21 | // PLEASE READ BEFORE CHANGING ANY ACCOUNTING OR MATH 22 | // Anytime there is division, there is a risk of numerical instability from rounding errors. In 23 | // order to minimize this risk, we adhere to the following guidelines: 24 | // 1) The conversion rate adopted is the number of gons that equals 1 fragment. 25 | // The inverse rate must not be used--TOTAL_GONS is always the numerator and _totalSupply is 26 | // always the denominator. (i.e. If you want to convert gons to fragments instead of 27 | // multiplying by the inverse rate, you should divide by the normal rate) 28 | // 2) Gon balances converted into Fragments are always rounded down (truncated). 29 | // 30 | // We make the following guarantees: 31 | // - If address 'A' transfers x Fragments to address 'B'. A's resulting external balance will 32 | // be decreased by precisely x Fragments, and B's external balance will be precisely 33 | // increased by x Fragments. 34 | // 35 | // We do not guarantee that the sum of all balances equals the result of calling totalSupply(). 36 | // This is because, for any conversion function 'f()' that has non-zero rounding error, 37 | // f(x0) + f(x1) + ... + f(xn) is not always equal to f(x0 + x1 + ... xn). 38 | using SafeMath for uint256; 39 | using SafeMathInt for int256; 40 | 41 | event LogRebase(uint256 indexed epoch, uint256 totalSupply); 42 | event LogMonetaryPolicyUpdated(address monetaryPolicy); 43 | 44 | // Used for authentication 45 | address public monetaryPolicy; 46 | 47 | modifier onlyMonetaryPolicy() { 48 | require(msg.sender == monetaryPolicy); 49 | _; 50 | } 51 | 52 | bool private rebasePausedDeprecated; 53 | bool private tokenPausedDeprecated; 54 | 55 | modifier validRecipient(address to) { 56 | require(to != address(0x0)); 57 | require(to != address(this)); 58 | _; 59 | } 60 | 61 | uint256 private constant DECIMALS = 9; 62 | uint256 private constant MAX_UINT256 = type(uint256).max; 63 | uint256 private constant INITIAL_FRAGMENTS_SUPPLY = 50 * 10**6 * 10**DECIMALS; 64 | 65 | // TOTAL_GONS is a multiple of INITIAL_FRAGMENTS_SUPPLY so that _gonsPerFragment is an integer. 66 | // Use the highest value that fits in a uint256 for max granularity. 67 | uint256 private constant TOTAL_GONS = MAX_UINT256 - (MAX_UINT256 % INITIAL_FRAGMENTS_SUPPLY); 68 | 69 | // MAX_SUPPLY = maximum integer < (sqrt(4*TOTAL_GONS + 1) - 1) / 2 70 | uint256 private constant MAX_SUPPLY = type(uint128).max; // (2^128) - 1 71 | 72 | uint256 private _totalSupply; 73 | uint256 private _gonsPerFragment; 74 | mapping(address => uint256) private _gonBalances; 75 | 76 | // This is denominated in Fragments, because the gons-fragments conversion might change before 77 | // it's fully paid. 78 | mapping(address => mapping(address => uint256)) private _allowedFragments; 79 | 80 | // EIP-2612: permit – 712-signed approvals 81 | // https://eips.ethereum.org/EIPS/eip-2612 82 | string public constant EIP712_REVISION = "1"; 83 | bytes32 public constant EIP712_DOMAIN = 84 | keccak256( 85 | "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" 86 | ); 87 | bytes32 public constant PERMIT_TYPEHASH = 88 | keccak256( 89 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 90 | ); 91 | 92 | // EIP-2612: keeps track of number of permits per address 93 | mapping(address => uint256) private _nonces; 94 | 95 | /** 96 | * @param monetaryPolicy_ The address of the monetary policy contract to use for authentication. 97 | */ 98 | function setMonetaryPolicy(address monetaryPolicy_) external onlyOwner { 99 | monetaryPolicy = monetaryPolicy_; 100 | emit LogMonetaryPolicyUpdated(monetaryPolicy_); 101 | } 102 | 103 | /** 104 | * @dev Notifies Fragments contract about a new rebase cycle. 105 | * @param supplyDelta The number of new fragment tokens to add into circulation via expansion. 106 | * @return The total number of fragments after the supply adjustment. 107 | */ 108 | function rebase(uint256 epoch, int256 supplyDelta) 109 | external 110 | onlyMonetaryPolicy 111 | returns (uint256) 112 | { 113 | if (supplyDelta == 0) { 114 | emit LogRebase(epoch, _totalSupply); 115 | return _totalSupply; 116 | } 117 | 118 | if (supplyDelta < 0) { 119 | _totalSupply = _totalSupply.sub(uint256(supplyDelta.abs())); 120 | } else { 121 | _totalSupply = _totalSupply.add(uint256(supplyDelta)); 122 | } 123 | 124 | if (_totalSupply > MAX_SUPPLY) { 125 | _totalSupply = MAX_SUPPLY; 126 | } 127 | 128 | _gonsPerFragment = TOTAL_GONS.div(_totalSupply); 129 | 130 | // From this point forward, _gonsPerFragment is taken as the source of truth. 131 | // We recalculate a new _totalSupply to be in agreement with the _gonsPerFragment 132 | // conversion rate. 133 | // This means our applied supplyDelta can deviate from the requested supplyDelta, 134 | // but this deviation is guaranteed to be < (_totalSupply^2)/(TOTAL_GONS - _totalSupply). 135 | // 136 | // In the case of _totalSupply <= MAX_UINT128 (our current supply cap), this 137 | // deviation is guaranteed to be < 1, so we can omit this step. If the supply cap is 138 | // ever increased, it must be re-included. 139 | // _totalSupply = TOTAL_GONS.div(_gonsPerFragment) 140 | 141 | emit LogRebase(epoch, _totalSupply); 142 | return _totalSupply; 143 | } 144 | 145 | function initialize(address owner_) public override initializer { 146 | ERC20Detailed.initialize("Ampleforth", "AMPL", uint8(DECIMALS)); 147 | Ownable.initialize(owner_); 148 | 149 | rebasePausedDeprecated = false; 150 | tokenPausedDeprecated = false; 151 | 152 | _totalSupply = INITIAL_FRAGMENTS_SUPPLY; 153 | _gonBalances[owner_] = TOTAL_GONS; 154 | _gonsPerFragment = TOTAL_GONS.div(_totalSupply); 155 | 156 | emit Transfer(address(0x0), owner_, _totalSupply); 157 | } 158 | 159 | /** 160 | * @return The total number of fragments. 161 | */ 162 | function totalSupply() external view override returns (uint256) { 163 | return _totalSupply; 164 | } 165 | 166 | /** 167 | * @param who The address to query. 168 | * @return The balance of the specified address. 169 | */ 170 | function balanceOf(address who) external view override returns (uint256) { 171 | return _gonBalances[who].div(_gonsPerFragment); 172 | } 173 | 174 | /** 175 | * @param who The address to query. 176 | * @return The gon balance of the specified address. 177 | */ 178 | function scaledBalanceOf(address who) external view returns (uint256) { 179 | return _gonBalances[who]; 180 | } 181 | 182 | /** 183 | * @return the total number of gons. 184 | */ 185 | function scaledTotalSupply() external pure returns (uint256) { 186 | return TOTAL_GONS; 187 | } 188 | 189 | /** 190 | * @return The number of successful permits by the specified address. 191 | */ 192 | function nonces(address who) public view returns (uint256) { 193 | return _nonces[who]; 194 | } 195 | 196 | /** 197 | * @return The computed DOMAIN_SEPARATOR to be used off-chain services 198 | * which implement EIP-712. 199 | * https://eips.ethereum.org/EIPS/eip-2612 200 | */ 201 | function DOMAIN_SEPARATOR() public view returns (bytes32) { 202 | uint256 chainId; 203 | assembly { 204 | chainId := chainid() 205 | } 206 | return 207 | keccak256( 208 | abi.encode( 209 | EIP712_DOMAIN, 210 | keccak256(bytes(name())), 211 | keccak256(bytes(EIP712_REVISION)), 212 | chainId, 213 | address(this) 214 | ) 215 | ); 216 | } 217 | 218 | /** 219 | * @dev Transfer tokens to a specified address. 220 | * @param to The address to transfer to. 221 | * @param value The amount to be transferred. 222 | * @return True on success, false otherwise. 223 | */ 224 | function transfer(address to, uint256 value) 225 | external 226 | override 227 | validRecipient(to) 228 | returns (bool) 229 | { 230 | uint256 gonValue = value.mul(_gonsPerFragment); 231 | 232 | _gonBalances[msg.sender] = _gonBalances[msg.sender].sub(gonValue); 233 | _gonBalances[to] = _gonBalances[to].add(gonValue); 234 | 235 | emit Transfer(msg.sender, to, value); 236 | return true; 237 | } 238 | 239 | /** 240 | * @dev Transfer all of the sender's wallet balance to a specified address. 241 | * @param to The address to transfer to. 242 | * @return True on success, false otherwise. 243 | */ 244 | function transferAll(address to) external validRecipient(to) returns (bool) { 245 | uint256 gonValue = _gonBalances[msg.sender]; 246 | uint256 value = gonValue.div(_gonsPerFragment); 247 | 248 | delete _gonBalances[msg.sender]; 249 | _gonBalances[to] = _gonBalances[to].add(gonValue); 250 | 251 | emit Transfer(msg.sender, to, value); 252 | return true; 253 | } 254 | 255 | /** 256 | * @dev Function to check the amount of tokens that an owner has allowed to a spender. 257 | * @param owner_ The address which owns the funds. 258 | * @param spender The address which will spend the funds. 259 | * @return The number of tokens still available for the spender. 260 | */ 261 | function allowance(address owner_, address spender) external view override returns (uint256) { 262 | return _allowedFragments[owner_][spender]; 263 | } 264 | 265 | /** 266 | * @dev Transfer tokens from one address to another. 267 | * @param from The address you want to send tokens from. 268 | * @param to The address you want to transfer to. 269 | * @param value The amount of tokens to be transferred. 270 | */ 271 | function transferFrom( 272 | address from, 273 | address to, 274 | uint256 value 275 | ) external override validRecipient(to) returns (bool) { 276 | _allowedFragments[from][msg.sender] = _allowedFragments[from][msg.sender].sub(value); 277 | 278 | uint256 gonValue = value.mul(_gonsPerFragment); 279 | _gonBalances[from] = _gonBalances[from].sub(gonValue); 280 | _gonBalances[to] = _gonBalances[to].add(gonValue); 281 | 282 | emit Transfer(from, to, value); 283 | return true; 284 | } 285 | 286 | /** 287 | * @dev Transfer all balance tokens from one address to another. 288 | * @param from The address you want to send tokens from. 289 | * @param to The address you want to transfer to. 290 | */ 291 | function transferAllFrom(address from, address to) external validRecipient(to) returns (bool) { 292 | uint256 gonValue = _gonBalances[from]; 293 | uint256 value = gonValue.div(_gonsPerFragment); 294 | 295 | _allowedFragments[from][msg.sender] = _allowedFragments[from][msg.sender].sub(value); 296 | 297 | delete _gonBalances[from]; 298 | _gonBalances[to] = _gonBalances[to].add(gonValue); 299 | 300 | emit Transfer(from, to, value); 301 | return true; 302 | } 303 | 304 | /** 305 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of 306 | * msg.sender. This method is included for ERC20 compatibility. 307 | * increaseAllowance and decreaseAllowance should be used instead. 308 | * Changing an allowance with this method brings the risk that someone may transfer both 309 | * the old and the new allowance - if they are both greater than zero - if a transfer 310 | * transaction is mined before the later approve() call is mined. 311 | * 312 | * @param spender The address which will spend the funds. 313 | * @param value The amount of tokens to be spent. 314 | */ 315 | function approve(address spender, uint256 value) external override returns (bool) { 316 | _allowedFragments[msg.sender][spender] = value; 317 | 318 | emit Approval(msg.sender, spender, value); 319 | return true; 320 | } 321 | 322 | /** 323 | * @dev Increase the amount of tokens that an owner has allowed to a spender. 324 | * This method should be used instead of approve() to avoid the double approval vulnerability 325 | * described above. 326 | * @param spender The address which will spend the funds. 327 | * @param addedValue The amount of tokens to increase the allowance by. 328 | */ 329 | function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { 330 | _allowedFragments[msg.sender][spender] = _allowedFragments[msg.sender][spender].add( 331 | addedValue 332 | ); 333 | 334 | emit Approval(msg.sender, spender, _allowedFragments[msg.sender][spender]); 335 | return true; 336 | } 337 | 338 | /** 339 | * @dev Decrease the amount of tokens that an owner has allowed to a spender. 340 | * 341 | * @param spender The address which will spend the funds. 342 | * @param subtractedValue The amount of tokens to decrease the allowance by. 343 | */ 344 | function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool) { 345 | uint256 oldValue = _allowedFragments[msg.sender][spender]; 346 | _allowedFragments[msg.sender][spender] = (subtractedValue >= oldValue) 347 | ? 0 348 | : oldValue.sub(subtractedValue); 349 | 350 | emit Approval(msg.sender, spender, _allowedFragments[msg.sender][spender]); 351 | return true; 352 | } 353 | 354 | /** 355 | * @dev Allows for approvals to be made via secp256k1 signatures. 356 | * @param owner The owner of the funds 357 | * @param spender The spender 358 | * @param value The amount 359 | * @param deadline The deadline timestamp, type(uint256).max for max deadline 360 | * @param v Signature param 361 | * @param s Signature param 362 | * @param r Signature param 363 | */ 364 | function permit( 365 | address owner, 366 | address spender, 367 | uint256 value, 368 | uint256 deadline, 369 | uint8 v, 370 | bytes32 r, 371 | bytes32 s 372 | ) public { 373 | require(block.timestamp <= deadline); 374 | 375 | uint256 ownerNonce = _nonces[owner]; 376 | bytes32 permitDataDigest = keccak256( 377 | abi.encode(PERMIT_TYPEHASH, owner, spender, value, ownerNonce, deadline) 378 | ); 379 | bytes32 digest = keccak256( 380 | abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), permitDataDigest) 381 | ); 382 | 383 | require(owner == ecrecover(digest, v, r, s)); 384 | 385 | _nonces[owner] = ownerNonce.add(1); 386 | 387 | _allowedFragments[owner][spender] = value; 388 | emit Approval(owner, spender, value); 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /contracts/UFragmentsPolicy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.4; 3 | 4 | import "./_external/SafeMath.sol"; 5 | import "./_external/Ownable.sol"; 6 | 7 | import "./lib/SafeMathInt.sol"; 8 | import "./lib/UInt256Lib.sol"; 9 | 10 | interface IUFragments { 11 | function totalSupply() external view returns (uint256); 12 | 13 | function rebase(uint256 epoch, int256 supplyDelta) external returns (uint256); 14 | } 15 | 16 | interface IOracle { 17 | function getData() external returns (uint256, bool); 18 | } 19 | 20 | /** 21 | * @title uFragments Monetary Supply Policy 22 | * @dev This is an implementation of the uFragments Ideal Money protocol. 23 | * 24 | * This component regulates the token supply of the uFragments ERC20 token in response to 25 | * market oracles. 26 | */ 27 | contract UFragmentsPolicy is Ownable { 28 | using SafeMath for uint256; 29 | using SafeMathInt for int256; 30 | using UInt256Lib for uint256; 31 | 32 | /// @notice DEPRECATED. 33 | event LogRebase( 34 | uint256 indexed epoch, 35 | uint256 exchangeRate, 36 | uint256 cpi, 37 | int256 requestedSupplyAdjustment, 38 | uint256 timestampSec 39 | ); 40 | 41 | event LogRebaseV2( 42 | uint256 indexed epoch, 43 | uint256 exchangeRate, 44 | uint256 targetRate, 45 | int256 requestedSupplyAdjustment 46 | ); 47 | 48 | IUFragments public uFrags; 49 | 50 | // Provides the cpi adjusted price target, as an 18 decimal fixed point number. 51 | IOracle public cpiOracle; 52 | 53 | // Market oracle provides the token/USD exchange rate as an 18 decimal fixed point number. 54 | // (eg) An oracle value of 1.5e18 it would mean 1 Ample is trading for $1.50. 55 | IOracle public marketOracle; 56 | 57 | /// @custom:oz-renamed-from baseCpi 58 | uint256 private baseCpi_DEPRECATED; 59 | 60 | // If the current exchange rate is within this fractional distance from the target, no supply 61 | // update is performed. Fixed point number--same format as the rate. 62 | // (ie) abs(rate - targetRate) / targetRate < deviationThreshold, then no supply change. 63 | // DECIMALS Fixed point number. 64 | uint256 public deviationThreshold; 65 | 66 | uint256 private rebaseLagDeprecated; 67 | 68 | // More than this much time must pass between rebase operations. 69 | uint256 public minRebaseTimeIntervalSec; 70 | 71 | // Block timestamp of last rebase operation 72 | uint256 public lastRebaseTimestampSec; 73 | 74 | // The rebase window begins this many seconds into the minRebaseTimeInterval period. 75 | // For example if minRebaseTimeInterval is 24hrs, it represents the time of day in seconds. 76 | uint256 public rebaseWindowOffsetSec; 77 | 78 | // The length of the time window where a rebase operation is allowed to execute, in seconds. 79 | uint256 public rebaseWindowLengthSec; 80 | 81 | // The number of rebase cycles since inception 82 | uint256 public epoch; 83 | 84 | uint256 private constant DECIMALS = 18; 85 | 86 | // Due to the expression in computeSupplyDelta(), MAX_RATE * MAX_SUPPLY must fit into an int256. 87 | // Both are 18 decimals fixed point numbers. 88 | uint256 private constant MAX_RATE = 10**6 * 10**DECIMALS; 89 | // MAX_SUPPLY = MAX_INT256 / MAX_RATE 90 | uint256 private constant MAX_SUPPLY = uint256(type(int256).max) / MAX_RATE; 91 | 92 | // This module orchestrates the rebase execution and downstream notification. 93 | address public orchestrator; 94 | 95 | // DECIMALS decimal fixed point numbers. 96 | // Used in computation of (Upper-Lower)/(1-(Upper/Lower)/2^(Growth*delta))) + Lower 97 | /// @custom:oz-renamed-from rebaseFunctionLowerPercentage 98 | int256 public rebaseFunctionNegativePercentageLimit; 99 | /// @custom:oz-renamed-from rebaseFunctionUpperPercentage 100 | int256 public rebaseFunctionPositivePercentageLimit; 101 | /// @custom:oz-renamed-from rebaseFunctionGrowth 102 | int256 public rebaseFunctionPositiveGrowth; 103 | int256 public rebaseFunctionNegativeGrowth; 104 | 105 | int256 private constant ONE = int256(10**DECIMALS); 106 | 107 | modifier onlyOrchestrator() { 108 | require(msg.sender == orchestrator); 109 | _; 110 | } 111 | 112 | /** 113 | * @notice Initiates a new rebase operation, provided the minimum time period has elapsed. 114 | * @dev Changes supply with percentage of: 115 | * (Upper-Lower)/(1-(Upper/Lower)/2^(Growth*NormalizedPriceDelta))) + Lower 116 | */ 117 | function rebase() external onlyOrchestrator { 118 | require(inRebaseWindow()); 119 | 120 | // This comparison also ensures there is no reentrancy. 121 | require(lastRebaseTimestampSec.add(minRebaseTimeIntervalSec) < block.timestamp); 122 | 123 | // Snap the rebase time to the start of this window. 124 | lastRebaseTimestampSec = block 125 | .timestamp 126 | .sub(block.timestamp.mod(minRebaseTimeIntervalSec)) 127 | .add(rebaseWindowOffsetSec); 128 | 129 | epoch = epoch.add(1); 130 | 131 | (uint256 targetRate, bool targetRateValid) = getTargetRate(); 132 | (uint256 exchangeRate, bool rateValid) = getExchangeRate(); 133 | if (exchangeRate > MAX_RATE) { 134 | exchangeRate = MAX_RATE; 135 | } 136 | 137 | int256 supplyDelta = (targetRateValid && rateValid) 138 | ? computeSupplyDelta(exchangeRate, targetRate) 139 | : int256(0); 140 | if (supplyDelta > 0 && uFrags.totalSupply().add(uint256(supplyDelta)) > MAX_SUPPLY) { 141 | supplyDelta = (MAX_SUPPLY.sub(uFrags.totalSupply())).toInt256Safe(); 142 | } 143 | 144 | uint256 supplyAfterRebase = uFrags.rebase(epoch, supplyDelta); 145 | assert(supplyAfterRebase <= MAX_SUPPLY); 146 | emit LogRebaseV2(epoch, exchangeRate, targetRate, supplyDelta); 147 | } 148 | 149 | /** 150 | * @notice Sets the reference to the CPI oracle. 151 | * @param cpiOracle_ The address of the cpi oracle contract. 152 | */ 153 | function setCpiOracle(IOracle cpiOracle_) external onlyOwner { 154 | cpiOracle = cpiOracle_; 155 | } 156 | 157 | /** 158 | * @notice Sets the reference to the market oracle. 159 | * @param marketOracle_ The address of the market oracle contract. 160 | */ 161 | function setMarketOracle(IOracle marketOracle_) external onlyOwner { 162 | marketOracle = marketOracle_; 163 | } 164 | 165 | /** 166 | * @notice Sets the reference to the orchestrator. 167 | * @param orchestrator_ The address of the orchestrator contract. 168 | */ 169 | function setOrchestrator(address orchestrator_) external onlyOwner { 170 | orchestrator = orchestrator_; 171 | } 172 | 173 | function setRebaseFunctionNegativePercentageLimit(int256 rebaseFunctionNegativePercentageLimit_) 174 | external 175 | onlyOwner 176 | { 177 | require(rebaseFunctionNegativePercentageLimit_ <= 0); 178 | rebaseFunctionNegativePercentageLimit = rebaseFunctionNegativePercentageLimit_; 179 | } 180 | 181 | function setRebaseFunctionPositivePercentageLimit(int256 rebaseFunctionPositivePercentageLimit_) 182 | external 183 | onlyOwner 184 | { 185 | require(rebaseFunctionPositivePercentageLimit_ >= 0); 186 | rebaseFunctionPositivePercentageLimit = rebaseFunctionPositivePercentageLimit_; 187 | } 188 | 189 | function setRebaseFunctionPositiveGrowth(int256 rebaseFunctionPositiveGrowth_) external onlyOwner { 190 | require(rebaseFunctionPositiveGrowth_ >= 0); 191 | rebaseFunctionPositiveGrowth = rebaseFunctionPositiveGrowth_; 192 | } 193 | 194 | function setRebaseFunctionNegativeGrowth(int256 rebaseFunctionNegativeGrowth_) external onlyOwner { 195 | require(rebaseFunctionNegativeGrowth_ >= 0); 196 | rebaseFunctionNegativeGrowth = rebaseFunctionNegativeGrowth_; 197 | } 198 | 199 | /** 200 | * @notice Sets the deviation threshold fraction. If the exchange rate given by the market 201 | * oracle is within this fractional distance from the targetRate, then no supply 202 | * modifications are made. DECIMALS fixed point number. 203 | * @param deviationThreshold_ The new exchange rate threshold fraction. 204 | */ 205 | function setDeviationThreshold(uint256 deviationThreshold_) external onlyOwner { 206 | deviationThreshold = deviationThreshold_; 207 | } 208 | 209 | /** 210 | * @notice Sets the parameters which control the timing and frequency of 211 | * rebase operations. 212 | * a) the minimum time period that must elapse between rebase cycles. 213 | * b) the rebase window offset parameter. 214 | * c) the rebase window length parameter. 215 | * @param minRebaseTimeIntervalSec_ More than this much time must pass between rebase 216 | * operations, in seconds. 217 | * @param rebaseWindowOffsetSec_ The number of seconds from the beginning of 218 | the rebase interval, where the rebase window begins. 219 | * @param rebaseWindowLengthSec_ The length of the rebase window in seconds. 220 | */ 221 | function setRebaseTimingParameters( 222 | uint256 minRebaseTimeIntervalSec_, 223 | uint256 rebaseWindowOffsetSec_, 224 | uint256 rebaseWindowLengthSec_ 225 | ) external onlyOwner { 226 | require(minRebaseTimeIntervalSec_ > 0); 227 | require(rebaseWindowOffsetSec_ < minRebaseTimeIntervalSec_); 228 | 229 | minRebaseTimeIntervalSec = minRebaseTimeIntervalSec_; 230 | rebaseWindowOffsetSec = rebaseWindowOffsetSec_; 231 | rebaseWindowLengthSec = rebaseWindowLengthSec_; 232 | } 233 | 234 | /** 235 | * @notice A multi-chain AMPL interface method. The Ampleforth monetary policy contract 236 | * on the base-chain and XC-AmpleController contracts on the satellite-chains 237 | * implement this method. It atomically returns two values: 238 | * what the current contract believes to be, 239 | * the globalAmpleforthEpoch and globalAMPLSupply. 240 | * @return globalAmpleforthEpoch The current epoch number. 241 | * @return globalAMPLSupply The total supply at the current epoch. 242 | */ 243 | function globalAmpleforthEpochAndAMPLSupply() external view returns (uint256, uint256) { 244 | return (epoch, uFrags.totalSupply()); 245 | } 246 | 247 | /** 248 | * @dev ZOS upgradable contract initialization method. 249 | * It is called at the time of contract creation to invoke parent class initializers and 250 | * initialize the contract's state variables. 251 | */ 252 | function initialize(address owner_, IUFragments uFrags_) public initializer { 253 | Ownable.initialize(owner_); 254 | 255 | // deviationThreshold = 0.05e18 = 5e16 256 | deviationThreshold = 25 * 10**(DECIMALS - 3); 257 | 258 | rebaseFunctionPositiveGrowth = int256(45 * (10**DECIMALS)); // Positive growth 259 | rebaseFunctionNegativeGrowth = int256(45 * (10**DECIMALS)); // Negative growth 260 | rebaseFunctionPositivePercentageLimit = int256(5 * (10**(DECIMALS - 2))); // 0.05 261 | rebaseFunctionNegativePercentageLimit = int256((-77) * int256(10**(DECIMALS - 3))); // -0.077 262 | 263 | minRebaseTimeIntervalSec = 1 days; 264 | rebaseWindowOffsetSec = 7200; // 2AM UTC 265 | rebaseWindowLengthSec = 20 minutes; 266 | 267 | lastRebaseTimestampSec = 0; 268 | epoch = 0; 269 | 270 | uFrags = uFrags_; 271 | } 272 | 273 | /** 274 | * @return The current price target and validity from the cpi oracle. 275 | */ 276 | function getTargetRate() public returns (uint256, bool) { 277 | return cpiOracle.getData(); 278 | } 279 | 280 | /** 281 | * @return The current exchange rate and validity from the market oracle. 282 | */ 283 | function getExchangeRate() public returns (uint256, bool) { 284 | return marketOracle.getData(); 285 | } 286 | 287 | /** 288 | * @return If the latest block timestamp is within the rebase time window it, returns true. 289 | * Otherwise, returns false. 290 | */ 291 | function inRebaseWindow() public view returns (bool) { 292 | return (block.timestamp.mod(minRebaseTimeIntervalSec) >= rebaseWindowOffsetSec && 293 | block.timestamp.mod(minRebaseTimeIntervalSec) < 294 | (rebaseWindowOffsetSec.add(rebaseWindowLengthSec))); 295 | } 296 | 297 | /** 298 | * Computes the percentage of supply to be added or removed: 299 | * Using the function in https://github.com/ampleforth/AIPs/blob/master/AIPs/aip-5.md 300 | * @param normalizedRate value of rate/targetRate in DECIMALS decimal fixed point number 301 | * @return The percentage of supply to be added or removed. 302 | */ 303 | function computeRebasePercentage( 304 | int256 normalizedRate, 305 | int256 lower, 306 | int256 upper, 307 | int256 growth 308 | ) public pure returns (int256) { 309 | int256 delta; 310 | 311 | delta = (normalizedRate.sub(ONE)); 312 | 313 | // Compute: (Upper-Lower)/(1-(Upper/Lower)/2^(Growth*delta))) + Lower 314 | 315 | int256 exponent = growth.mul(delta).div(ONE); 316 | // Cap exponent to guarantee it is not too big for twoPower 317 | if (exponent > ONE.mul(100)) { 318 | exponent = ONE.mul(100); 319 | } 320 | if (exponent < ONE.mul(-100)) { 321 | exponent = ONE.mul(-100); 322 | } 323 | 324 | int256 pow = SafeMathInt.twoPower(exponent, ONE); // 2^(Growth*Delta) 325 | if (pow == 0) { 326 | return lower; 327 | } 328 | int256 numerator = upper.sub(lower); //(Upper-Lower) 329 | int256 intermediate = upper.mul(ONE).div(lower); 330 | intermediate = intermediate.mul(ONE).div(pow); 331 | int256 denominator = ONE.sub(intermediate); // (1-(Upper/Lower)/2^(Growth*delta))) 332 | 333 | int256 rebasePercentage = (numerator.mul(ONE).div(denominator)).add(lower); 334 | return rebasePercentage; 335 | } 336 | 337 | /** 338 | * @return Computes the total supply adjustment in response to the exchange rate 339 | * and the targetRate. 340 | */ 341 | function computeSupplyDelta(uint256 rate, uint256 targetRate) internal view returns (int256) { 342 | if (withinDeviationThreshold(rate, targetRate)) { 343 | return 0; 344 | } 345 | int256 targetRateSigned = targetRate.toInt256Safe(); 346 | int256 normalizedRate = rate.toInt256Safe().mul(ONE).div(targetRateSigned); 347 | 348 | // Determine growth and bounds based on positive or negative rebase 349 | int256 rebasePercentage; 350 | if (normalizedRate >= ONE) { 351 | rebasePercentage = computeRebasePercentage( 352 | normalizedRate, 353 | -rebaseFunctionPositivePercentageLimit, 354 | rebaseFunctionPositivePercentageLimit, 355 | rebaseFunctionPositiveGrowth 356 | ); 357 | } else { 358 | rebasePercentage = computeRebasePercentage( 359 | normalizedRate, 360 | rebaseFunctionNegativePercentageLimit, 361 | -rebaseFunctionNegativePercentageLimit, 362 | rebaseFunctionNegativeGrowth 363 | ); 364 | } 365 | 366 | return uFrags.totalSupply().toInt256Safe().mul(rebasePercentage).div(ONE); 367 | } 368 | 369 | /** 370 | * @param rate The current exchange rate, an 18 decimal fixed point number. 371 | * @param targetRate The target exchange rate, an 18 decimal fixed point number. 372 | * @return If the rate is within the deviation threshold from the target rate, returns true. 373 | * Otherwise, returns false. 374 | */ 375 | function withinDeviationThreshold(uint256 rate, uint256 targetRate) 376 | internal 377 | view 378 | returns (bool) 379 | { 380 | uint256 absoluteDeviationThreshold = targetRate.mul(deviationThreshold).div(10**DECIMALS); 381 | 382 | return 383 | (rate >= targetRate && rate.sub(targetRate) < absoluteDeviationThreshold) || 384 | (rate < targetRate && targetRate.sub(rate) < absoluteDeviationThreshold); 385 | } 386 | 387 | /** 388 | * To maintain abi backward compatibility 389 | */ 390 | function rebaseLag() public pure returns (uint256) { 391 | return 1; 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /contracts/WAMPL.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity 0.8.4; 3 | 4 | import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 5 | // solhint-disable-next-line max-line-length 6 | import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; 7 | import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 8 | 9 | // solhint-disable-next-line max-line-length 10 | import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; 11 | 12 | /** 13 | * @title WAMPL (Wrapped AMPL). 14 | * 15 | * @dev A fixed-balance ERC-20 wrapper for the AMPL rebasing token. 16 | * 17 | * Users deposit AMPL into this contract and are minted wAMPL. 18 | * 19 | * Each account's wAMPL balance represents the fixed percentage ownership 20 | * of AMPL's market cap. 21 | * 22 | * For example: 100K wAMPL => 1% of the AMPL market cap 23 | * when the AMPL supply is 100M, 100K wAMPL will be redeemable for 1M AMPL 24 | * when the AMPL supply is 500M, 100K wAMPL will be redeemable for 5M AMPL 25 | * and so on. 26 | * 27 | * We call wAMPL the "wrapper" token and AMPL the "underlying" or "wrapped" token. 28 | */ 29 | contract WAMPL is ERC20Upgradeable, ERC20PermitUpgradeable { 30 | using SafeERC20Upgradeable for IERC20Upgradeable; 31 | 32 | //-------------------------------------------------------------------------- 33 | // Constants 34 | 35 | /// @dev The maximum wAMPL supply. 36 | uint256 public constant MAX_WAMPL_SUPPLY = 10000000 * (10**18); // 10 M 37 | 38 | //-------------------------------------------------------------------------- 39 | // Attributes 40 | 41 | /// @dev The reference to the AMPL token. 42 | address private immutable _ampl; 43 | 44 | //-------------------------------------------------------------------------- 45 | 46 | /// @notice Contract constructor. 47 | /// @param ampl The AMPL ERC20 token address. 48 | constructor(address ampl) { 49 | _ampl = ampl; 50 | } 51 | 52 | /// @notice Contract state initialization. 53 | /// @param name_ The wAMPL ERC20 name. 54 | /// @param symbol_ The wAMPL ERC20 symbol. 55 | function init(string memory name_, string memory symbol_) public initializer { 56 | __ERC20_init(name_, symbol_); 57 | __ERC20Permit_init(name_); 58 | } 59 | 60 | //-------------------------------------------------------------------------- 61 | // WAMPL write methods 62 | 63 | /// @notice Transfers AMPLs from {msg.sender} and mints wAMPLs. 64 | /// 65 | /// @param wamples The amount of wAMPLs to mint. 66 | /// @return The amount of AMPLs deposited. 67 | function mint(uint256 wamples) external returns (uint256) { 68 | uint256 amples = _wampleToAmple(wamples, _queryAMPLSupply()); 69 | _deposit(_msgSender(), _msgSender(), amples, wamples); 70 | return amples; 71 | } 72 | 73 | /// @notice Transfers AMPLs from {msg.sender} and mints wAMPLs, 74 | /// to the specified beneficiary. 75 | /// 76 | /// @param to The beneficiary wallet. 77 | /// @param wamples The amount of wAMPLs to mint. 78 | /// @return The amount of AMPLs deposited. 79 | function mintFor(address to, uint256 wamples) external returns (uint256) { 80 | uint256 amples = _wampleToAmple(wamples, _queryAMPLSupply()); 81 | _deposit(_msgSender(), to, amples, wamples); 82 | return amples; 83 | } 84 | 85 | /// @notice Burns wAMPLs from {msg.sender} and transfers AMPLs back. 86 | /// 87 | /// @param wamples The amount of wAMPLs to burn. 88 | /// @return The amount of AMPLs withdrawn. 89 | function burn(uint256 wamples) external returns (uint256) { 90 | uint256 amples = _wampleToAmple(wamples, _queryAMPLSupply()); 91 | _withdraw(_msgSender(), _msgSender(), amples, wamples); 92 | return amples; 93 | } 94 | 95 | /// @notice Burns wAMPLs from {msg.sender} and transfers AMPLs back, 96 | /// to the specified beneficiary. 97 | /// 98 | /// @param to The beneficiary wallet. 99 | /// @param wamples The amount of wAMPLs to burn. 100 | /// @return The amount of AMPLs withdrawn. 101 | function burnTo(address to, uint256 wamples) external returns (uint256) { 102 | uint256 amples = _wampleToAmple(wamples, _queryAMPLSupply()); 103 | _withdraw(_msgSender(), to, amples, wamples); 104 | return amples; 105 | } 106 | 107 | /// @notice Burns all wAMPLs from {msg.sender} and transfers AMPLs back. 108 | /// 109 | /// @return The amount of AMPLs withdrawn. 110 | function burnAll() external returns (uint256) { 111 | uint256 wamples = balanceOf(_msgSender()); 112 | uint256 amples = _wampleToAmple(wamples, _queryAMPLSupply()); 113 | _withdraw(_msgSender(), _msgSender(), amples, wamples); 114 | return amples; 115 | } 116 | 117 | /// @notice Burns all wAMPLs from {msg.sender} and transfers AMPLs back, 118 | /// to the specified beneficiary. 119 | /// 120 | /// @param to The beneficiary wallet. 121 | /// @return The amount of AMPLs withdrawn. 122 | function burnAllTo(address to) external returns (uint256) { 123 | uint256 wamples = balanceOf(_msgSender()); 124 | uint256 amples = _wampleToAmple(wamples, _queryAMPLSupply()); 125 | _withdraw(_msgSender(), to, amples, wamples); 126 | return amples; 127 | } 128 | 129 | /// @notice Transfers AMPLs from {msg.sender} and mints wAMPLs. 130 | /// 131 | /// @param amples The amount of AMPLs to deposit. 132 | /// @return The amount of wAMPLs minted. 133 | function deposit(uint256 amples) external returns (uint256) { 134 | uint256 wamples = _ampleToWample(amples, _queryAMPLSupply()); 135 | _deposit(_msgSender(), _msgSender(), amples, wamples); 136 | return wamples; 137 | } 138 | 139 | /// @notice Transfers AMPLs from {msg.sender} and mints wAMPLs, 140 | /// to the specified beneficiary. 141 | /// 142 | /// @param to The beneficiary wallet. 143 | /// @param amples The amount of AMPLs to deposit. 144 | /// @return The amount of wAMPLs minted. 145 | function depositFor(address to, uint256 amples) external returns (uint256) { 146 | uint256 wamples = _ampleToWample(amples, _queryAMPLSupply()); 147 | _deposit(_msgSender(), to, amples, wamples); 148 | return wamples; 149 | } 150 | 151 | /// @notice Burns wAMPLs from {msg.sender} and transfers AMPLs back. 152 | /// 153 | /// @param amples The amount of AMPLs to withdraw. 154 | /// @return The amount of burnt wAMPLs. 155 | function withdraw(uint256 amples) external returns (uint256) { 156 | uint256 wamples = _ampleToWample(amples, _queryAMPLSupply()); 157 | _withdraw(_msgSender(), _msgSender(), amples, wamples); 158 | return wamples; 159 | } 160 | 161 | /// @notice Burns wAMPLs from {msg.sender} and transfers AMPLs back, 162 | /// to the specified beneficiary. 163 | /// 164 | /// @param to The beneficiary wallet. 165 | /// @param amples The amount of AMPLs to withdraw. 166 | /// @return The amount of burnt wAMPLs. 167 | function withdrawTo(address to, uint256 amples) external returns (uint256) { 168 | uint256 wamples = _ampleToWample(amples, _queryAMPLSupply()); 169 | _withdraw(_msgSender(), to, amples, wamples); 170 | return wamples; 171 | } 172 | 173 | /// @notice Burns all wAMPLs from {msg.sender} and transfers AMPLs back. 174 | /// 175 | /// @return The amount of burnt wAMPLs. 176 | function withdrawAll() external returns (uint256) { 177 | uint256 wamples = balanceOf(_msgSender()); 178 | uint256 amples = _wampleToAmple(wamples, _queryAMPLSupply()); 179 | _withdraw(_msgSender(), _msgSender(), amples, wamples); 180 | return wamples; 181 | } 182 | 183 | /// @notice Burns all wAMPLs from {msg.sender} and transfers AMPLs back, 184 | /// to the specified beneficiary. 185 | /// 186 | /// @param to The beneficiary wallet. 187 | /// @return The amount of burnt wAMPLs. 188 | function withdrawAllTo(address to) external returns (uint256) { 189 | uint256 wamples = balanceOf(_msgSender()); 190 | uint256 amples = _wampleToAmple(wamples, _queryAMPLSupply()); 191 | _withdraw(_msgSender(), to, amples, wamples); 192 | return wamples; 193 | } 194 | 195 | //-------------------------------------------------------------------------- 196 | // WAMPL view methods 197 | 198 | /// @return The address of the underlying "wrapped" token ie) AMPL. 199 | function underlying() external view returns (address) { 200 | return _ampl; 201 | } 202 | 203 | /// @return The total AMPLs held by this contract. 204 | function totalUnderlying() external view returns (uint256) { 205 | return _wampleToAmple(totalSupply(), _queryAMPLSupply()); 206 | } 207 | 208 | /// @param owner The account address. 209 | /// @return The AMPL balance redeemable by the owner. 210 | function balanceOfUnderlying(address owner) external view returns (uint256) { 211 | return _wampleToAmple(balanceOf(owner), _queryAMPLSupply()); 212 | } 213 | 214 | /// @param amples The amount of AMPL tokens. 215 | /// @return The amount of wAMPL tokens exchangeable. 216 | function underlyingToWrapper(uint256 amples) external view returns (uint256) { 217 | return _ampleToWample(amples, _queryAMPLSupply()); 218 | } 219 | 220 | /// @param wamples The amount of wAMPL tokens. 221 | /// @return The amount of AMPL tokens exchangeable. 222 | function wrapperToUnderlying(uint256 wamples) external view returns (uint256) { 223 | return _wampleToAmple(wamples, _queryAMPLSupply()); 224 | } 225 | 226 | //-------------------------------------------------------------------------- 227 | // Private methods 228 | 229 | /// @dev Internal helper function to handle deposit state change. 230 | /// @param from The initiator wallet. 231 | /// @param to The beneficiary wallet. 232 | /// @param amples The amount of AMPLs to deposit. 233 | /// @param wamples The amount of wAMPLs to mint. 234 | function _deposit( 235 | address from, 236 | address to, 237 | uint256 amples, 238 | uint256 wamples 239 | ) private { 240 | IERC20Upgradeable(_ampl).safeTransferFrom(from, address(this), amples); 241 | 242 | _mint(to, wamples); 243 | } 244 | 245 | /// @dev Internal helper function to handle withdraw state change. 246 | /// @param from The initiator wallet. 247 | /// @param to The beneficiary wallet. 248 | /// @param amples The amount of AMPLs to withdraw. 249 | /// @param wamples The amount of wAMPLs to burn. 250 | function _withdraw( 251 | address from, 252 | address to, 253 | uint256 amples, 254 | uint256 wamples 255 | ) private { 256 | _burn(from, wamples); 257 | 258 | IERC20Upgradeable(_ampl).safeTransfer(to, amples); 259 | } 260 | 261 | /// @dev Queries the current total supply of AMPL. 262 | /// @return The current AMPL supply. 263 | function _queryAMPLSupply() private view returns (uint256) { 264 | return IERC20Upgradeable(_ampl).totalSupply(); 265 | } 266 | 267 | //-------------------------------------------------------------------------- 268 | // Pure methods 269 | 270 | /// @dev Converts AMPLs to wAMPL amount. 271 | function _ampleToWample(uint256 amples, uint256 totalAMPLSupply) 272 | private 273 | pure 274 | returns (uint256) 275 | { 276 | return (amples * MAX_WAMPL_SUPPLY) / totalAMPLSupply; 277 | } 278 | 279 | /// @dev Converts wAMPLs amount to AMPLs. 280 | function _wampleToAmple(uint256 wamples, uint256 totalAMPLSupply) 281 | private 282 | pure 283 | returns (uint256) 284 | { 285 | return (wamples * totalAMPLSupply) / MAX_WAMPL_SUPPLY; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /contracts/_external/ERC20Detailed.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "./Initializable.sol"; 4 | import "./IERC20.sol"; 5 | 6 | /** 7 | * @title ERC20Detailed token 8 | * @dev The decimals are only for visualization purposes. 9 | * All the operations are done using the smallest and indivisible token unit, 10 | * just as on Ethereum all the operations are done in wei. 11 | */ 12 | abstract contract ERC20Detailed is Initializable, IERC20 { 13 | string private _name; 14 | string private _symbol; 15 | uint8 private _decimals; 16 | 17 | function initialize( 18 | string memory name, 19 | string memory symbol, 20 | uint8 decimals 21 | ) public virtual initializer { 22 | _name = name; 23 | _symbol = symbol; 24 | _decimals = decimals; 25 | } 26 | 27 | /** 28 | * @return the name of the token. 29 | */ 30 | function name() public view returns (string memory) { 31 | return _name; 32 | } 33 | 34 | /** 35 | * @return the symbol of the token. 36 | */ 37 | function symbol() public view returns (string memory) { 38 | return _symbol; 39 | } 40 | 41 | /** 42 | * @return the number of decimals of the token. 43 | */ 44 | function decimals() public view returns (uint8) { 45 | return _decimals; 46 | } 47 | 48 | uint256[50] private ______gap; 49 | } 50 | -------------------------------------------------------------------------------- /contracts/_external/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | /** 4 | * @title ERC20 interface 5 | * @dev see https://github.com/ethereum/EIPs/issues/20 6 | */ 7 | interface IERC20 { 8 | function totalSupply() external view returns (uint256); 9 | 10 | function balanceOf(address who) external view returns (uint256); 11 | 12 | function allowance(address owner, address spender) external view returns (uint256); 13 | 14 | function transfer(address to, uint256 value) external returns (bool); 15 | 16 | function approve(address spender, uint256 value) external returns (bool); 17 | 18 | function transferFrom( 19 | address from, 20 | address to, 21 | uint256 value 22 | ) external returns (bool); 23 | 24 | event Transfer(address indexed from, address indexed to, uint256 value); 25 | 26 | event Approval(address indexed owner, address indexed spender, uint256 value); 27 | } 28 | -------------------------------------------------------------------------------- /contracts/_external/Initializable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | /** 4 | * @title Initializable 5 | * 6 | * @dev Helper contract to support initializer functions. To use it, replace 7 | * the constructor with a function that has the `initializer` modifier. 8 | * WARNING: Unlike constructors, initializer functions must be manually 9 | * invoked. This applies both to deploying an Initializable contract, as well 10 | * as extending an Initializable contract via inheritance. 11 | * WARNING: When used with inheritance, manual care must be taken to not invoke 12 | * a parent initializer twice, or ensure that all initializers are idempotent, 13 | * because this is not dealt with automatically as with constructors. 14 | */ 15 | contract Initializable { 16 | /** 17 | * @dev Indicates that the contract has been initialized. 18 | */ 19 | bool private initialized; 20 | 21 | /** 22 | * @dev Indicates that the contract is in the process of being initialized. 23 | */ 24 | bool private initializing; 25 | 26 | /** 27 | * @dev Modifier to use in the initializer function of a contract. 28 | */ 29 | modifier initializer() { 30 | require( 31 | initializing || isConstructor() || !initialized, 32 | "Contract instance has already been initialized" 33 | ); 34 | 35 | bool wasInitializing = initializing; 36 | initializing = true; 37 | initialized = true; 38 | 39 | _; 40 | 41 | initializing = wasInitializing; 42 | } 43 | 44 | /// @dev Returns true if and only if the function is running in the constructor 45 | function isConstructor() private view returns (bool) { 46 | // extcodesize checks the size of the code stored in an address, and 47 | // address returns the current address. Since the code is still not 48 | // deployed when running a constructor, any checks on its code size will 49 | // yield zero, making it an effective way to detect if a contract is 50 | // under construction or not. 51 | 52 | // MINOR CHANGE HERE: 53 | 54 | // previous code 55 | // uint256 cs; 56 | // assembly { cs := extcodesize(address) } 57 | // return cs == 0; 58 | 59 | // current code 60 | address _self = address(this); 61 | uint256 cs; 62 | assembly { 63 | cs := extcodesize(_self) 64 | } 65 | return cs == 0; 66 | } 67 | 68 | // Reserved storage space to allow for layout changes in the future. 69 | uint256[50] private ______gap; 70 | } 71 | -------------------------------------------------------------------------------- /contracts/_external/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "./Initializable.sol"; 4 | 5 | /** 6 | * @title Ownable 7 | * @dev The Ownable contract has an owner address, and provides basic authorization control 8 | * functions, this simplifies the implementation of "user permissions". 9 | */ 10 | contract Ownable is Initializable { 11 | address private _owner; 12 | 13 | event OwnershipRenounced(address indexed previousOwner); 14 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 15 | 16 | /** 17 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 18 | * account. 19 | */ 20 | function initialize(address sender) public virtual initializer { 21 | _owner = sender; 22 | } 23 | 24 | /** 25 | * @return the address of the owner. 26 | */ 27 | function owner() public view returns (address) { 28 | return _owner; 29 | } 30 | 31 | /** 32 | * @dev Throws if called by any account other than the owner. 33 | */ 34 | modifier onlyOwner() { 35 | require(isOwner()); 36 | _; 37 | } 38 | 39 | /** 40 | * @return true if `msg.sender` is the owner of the contract. 41 | */ 42 | function isOwner() public view returns (bool) { 43 | return msg.sender == _owner; 44 | } 45 | 46 | /** 47 | * @dev Allows the current owner to relinquish control of the contract. 48 | * @notice Renouncing to ownership will leave the contract without an owner. 49 | * It will not be possible to call the functions with the `onlyOwner` 50 | * modifier anymore. 51 | */ 52 | function renounceOwnership() public onlyOwner { 53 | emit OwnershipRenounced(_owner); 54 | _owner = address(0); 55 | } 56 | 57 | /** 58 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 59 | * @param newOwner The address to transfer ownership to. 60 | */ 61 | function transferOwnership(address newOwner) public onlyOwner { 62 | _transferOwnership(newOwner); 63 | } 64 | 65 | /** 66 | * @dev Transfers control of the contract to a newOwner. 67 | * @param newOwner The address to transfer ownership to. 68 | */ 69 | function _transferOwnership(address newOwner) internal { 70 | require(newOwner != address(0)); 71 | emit OwnershipTransferred(_owner, newOwner); 72 | _owner = newOwner; 73 | } 74 | 75 | uint256[50] private ______gap; 76 | } 77 | -------------------------------------------------------------------------------- /contracts/_external/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | /** 4 | * @title SafeMath 5 | * @dev Math operations with safety checks that revert on error 6 | */ 7 | library SafeMath { 8 | /** 9 | * @dev Multiplies two numbers, reverts on overflow. 10 | */ 11 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 12 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 13 | // benefit is lost if 'b' is also tested. 14 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 15 | if (a == 0) { 16 | return 0; 17 | } 18 | 19 | uint256 c = a * b; 20 | require(c / a == b); 21 | 22 | return c; 23 | } 24 | 25 | /** 26 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 27 | */ 28 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 29 | require(b > 0); // Solidity only automatically asserts when dividing by 0 30 | uint256 c = a / b; 31 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 32 | 33 | return c; 34 | } 35 | 36 | /** 37 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 38 | */ 39 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 40 | require(b <= a); 41 | uint256 c = a - b; 42 | 43 | return c; 44 | } 45 | 46 | /** 47 | * @dev Adds two numbers, reverts on overflow. 48 | */ 49 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 50 | uint256 c = a + b; 51 | require(c >= a); 52 | 53 | return c; 54 | } 55 | 56 | /** 57 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 58 | * reverts when dividing by zero. 59 | */ 60 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 61 | require(b != 0); 62 | return a % b; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/interfaces/IAMPL.sol: -------------------------------------------------------------------------------- 1 | // Public interface definition for the AMPL - ERC20 token on Ethereum (the base-chain) 2 | interface IAMPL { 3 | // ERC20 4 | function totalSupply() external view returns (uint256); 5 | 6 | function balanceOf(address who) external view returns (uint256); 7 | 8 | function allowance(address owner_, address spender) external view returns (uint256); 9 | 10 | function transfer(address to, uint256 value) external returns (bool); 11 | 12 | function approve(address spender, uint256 value) external returns (bool); 13 | 14 | function increaseAllowance(address spender, uint256 addedValue) external returns (bool); 15 | 16 | function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); 17 | 18 | function transferFrom( 19 | address from, 20 | address to, 21 | uint256 value 22 | ) external returns (bool); 23 | 24 | // EIP-2612 25 | function permit( 26 | address owner, 27 | address spender, 28 | uint256 value, 29 | uint256 deadline, 30 | uint8 v, 31 | bytes32 r, 32 | bytes32 s 33 | ) external; 34 | 35 | function nonces(address owner) external view returns (uint256); 36 | 37 | function DOMAIN_SEPARATOR() external view returns (bytes32); 38 | 39 | // Elastic token interface 40 | function scaledBalanceOf(address who) external view returns (uint256); 41 | 42 | function scaledTotalSupply() external view returns (uint256); 43 | 44 | function transferAll(address to) external returns (bool); 45 | 46 | function transferAllFrom(address from, address to) external returns (bool); 47 | } 48 | -------------------------------------------------------------------------------- /contracts/interfaces/IAmpleforth.sol: -------------------------------------------------------------------------------- 1 | // Public interface definition for the Ampleforth supply policy on Ethereum (the base-chain) 2 | interface IAmpleforth { 3 | function epoch() external view returns (uint256); 4 | 5 | function lastRebaseTimestampSec() external view returns (uint256); 6 | 7 | function inRebaseWindow() external view returns (bool); 8 | 9 | function globalAmpleforthEpochAndAMPLSupply() external view returns (uint256, uint256); 10 | 11 | function getTargetRate() external returns (uint256, bool); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IOrchestrator.sol: -------------------------------------------------------------------------------- 1 | // Public interface definition for the Ampleforth Orchestrator on Ethereum (the base-chain) 2 | interface IOrchestrator { 3 | function rebase() external; 4 | } 5 | -------------------------------------------------------------------------------- /contracts/lib/SafeMathInt.sol: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018 requestnetwork 5 | Copyright (c) 2018 Fragments, Inc. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | // SPDX-License-Identifier: GPL-3.0-or-later 27 | pragma solidity ^0.8.4; 28 | 29 | /** 30 | * @title SafeMathInt 31 | * @dev Math operations for int256 with overflow safety checks. 32 | */ 33 | library SafeMathInt { 34 | int256 private constant MIN_INT256 = int256(1) << 255; 35 | int256 private constant MAX_INT256 = ~(int256(1) << 255); 36 | 37 | /** 38 | * @dev Multiplies two int256 variables and fails on overflow. 39 | */ 40 | function mul(int256 a, int256 b) internal pure returns (int256) { 41 | int256 c = a * b; 42 | 43 | // Detect overflow when multiplying MIN_INT256 with -1 44 | require(c != MIN_INT256 || (a & MIN_INT256) != (b & MIN_INT256)); 45 | require((b == 0) || (c / b == a)); 46 | return c; 47 | } 48 | 49 | /** 50 | * @dev Division of two int256 variables and fails on overflow. 51 | */ 52 | function div(int256 a, int256 b) internal pure returns (int256) { 53 | // Prevent overflow when dividing MIN_INT256 by -1 54 | require(b != -1 || a != MIN_INT256); 55 | 56 | // Solidity already throws when dividing by 0. 57 | return a / b; 58 | } 59 | 60 | /** 61 | * @dev Subtracts two int256 variables and fails on overflow. 62 | */ 63 | function sub(int256 a, int256 b) internal pure returns (int256) { 64 | int256 c = a - b; 65 | require((b >= 0 && c <= a) || (b < 0 && c > a)); 66 | return c; 67 | } 68 | 69 | /** 70 | * @dev Adds two int256 variables and fails on overflow. 71 | */ 72 | function add(int256 a, int256 b) internal pure returns (int256) { 73 | int256 c = a + b; 74 | require((b >= 0 && c >= a) || (b < 0 && c < a)); 75 | return c; 76 | } 77 | 78 | /** 79 | * @dev Converts to absolute value, and fails on overflow. 80 | */ 81 | function abs(int256 a) internal pure returns (int256) { 82 | require(a != MIN_INT256); 83 | return a < 0 ? -a : a; 84 | } 85 | 86 | /** 87 | * @dev Computes 2^exp with limited precision where -100 <= exp <= 100 * one 88 | * @param one 1.0 represented in the same fixed point number format as exp 89 | * @param exp The power to raise 2 to -100 <= exp <= 100 * one 90 | * @return 2^exp represented with same number of decimals after the point as one 91 | */ 92 | function twoPower(int256 exp, int256 one) internal pure returns (int256) { 93 | bool reciprocal = false; 94 | if (exp < 0) { 95 | reciprocal = true; 96 | exp = abs(exp); 97 | } 98 | 99 | // Precomputed values for 2^(1/2^i) in 18 decimals fixed point numbers 100 | int256[5] memory ks = [ 101 | int256(1414213562373095049), 102 | 1189207115002721067, 103 | 1090507732665257659, 104 | 1044273782427413840, 105 | 1021897148654116678 106 | ]; 107 | int256 whole = div(exp, one); 108 | require(whole <= 100); 109 | int256 result = mul(int256(uint256(1) << uint256(whole)), one); 110 | int256 remaining = sub(exp, mul(whole, one)); 111 | 112 | int256 current = div(one, 2); 113 | for (uint256 i = 0; i < 5; i++) { 114 | if (remaining >= current) { 115 | remaining = sub(remaining, current); 116 | result = div(mul(result, ks[i]), 10**18); // 10**18 to match hardcoded ks values 117 | } 118 | current = div(current, 2); 119 | } 120 | if (reciprocal) { 121 | result = div(mul(one, one), result); 122 | } 123 | return result; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /contracts/lib/Select.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity ^0.8.4; 3 | 4 | /** 5 | * @title Select 6 | * @dev Median Selection Library 7 | */ 8 | library Select { 9 | /** 10 | * @dev Sorts the input array up to the denoted size, and returns the median. 11 | * @param array Input array to compute its median. 12 | * @param size Number of elements in array to compute the median for. 13 | * @return Median of array. 14 | */ 15 | function computeMedian(uint256[] memory array, uint256 size) internal pure returns (uint256) { 16 | require(size > 0 && array.length >= size); 17 | for (uint256 i = 1; i < size; i++) { 18 | for (uint256 j = i; j > 0 && array[j - 1] > array[j]; j--) { 19 | uint256 tmp = array[j]; 20 | array[j] = array[j - 1]; 21 | array[j - 1] = tmp; 22 | } 23 | } 24 | if (size % 2 == 1) { 25 | return array[size / 2]; 26 | } else { 27 | return (array[size / 2] + array[size / 2 - 1]) / 2; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/lib/UInt256Lib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity ^0.8.4; 3 | 4 | /** 5 | * @title Various utilities useful for uint256. 6 | */ 7 | library UInt256Lib { 8 | uint256 private constant MAX_INT256 = ~(uint256(1) << 255); 9 | 10 | /** 11 | * @dev Safely converts a uint256 to an int256. 12 | */ 13 | function toInt256Safe(uint256 a) internal pure returns (int256) { 14 | require(a <= MAX_INT256); 15 | return int256(a); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/mocks/ConstructorRebaseCallerContract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "../Orchestrator.sol"; 4 | 5 | contract ConstructorRebaseCallerContract { 6 | constructor(address orchestrator) public { 7 | // Take out a flash loan. 8 | // Do something funky... 9 | Orchestrator(orchestrator).rebase(); // should fail 10 | // pay back flash loan. 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/mocks/GetMedianOracleDataCallerContract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "../MedianOracle.sol"; 4 | 5 | contract GetMedianOracleDataCallerContract { 6 | event ReturnValueUInt256Bool(uint256 value, bool valid); 7 | 8 | IOracle public oracle; 9 | 10 | constructor() public {} 11 | 12 | function setOracle(IOracle _oracle) public { 13 | oracle = _oracle; 14 | } 15 | 16 | function getData() public returns (uint256) { 17 | uint256 _value; 18 | bool _valid; 19 | (_value, _valid) = oracle.getData(); 20 | emit ReturnValueUInt256Bool(_value, _valid); 21 | return _value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/mocks/Mock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | contract Mock { 4 | event FunctionCalled(string instanceName, string functionName, address caller); 5 | event FunctionArguments(uint256[] uintVals, int256[] intVals); 6 | event ReturnValueInt256(int256 val); 7 | event ReturnValueUInt256(uint256 val); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/mocks/MockDownstream.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "./Mock.sol"; 4 | 5 | contract MockDownstream is Mock { 6 | function updateNoArg() external returns (bool) { 7 | emit FunctionCalled("MockDownstream", "updateNoArg", msg.sender); 8 | uint256[] memory uintVals = new uint256[](0); 9 | int256[] memory intVals = new int256[](0); 10 | emit FunctionArguments(uintVals, intVals); 11 | return true; 12 | } 13 | 14 | function updateOneArg(uint256 u) external { 15 | emit FunctionCalled("MockDownstream", "updateOneArg", msg.sender); 16 | 17 | uint256[] memory uintVals = new uint256[](1); 18 | uintVals[0] = u; 19 | int256[] memory intVals = new int256[](0); 20 | emit FunctionArguments(uintVals, intVals); 21 | } 22 | 23 | function updateTwoArgs(uint256 u, int256 i) external { 24 | emit FunctionCalled("MockDownstream", "updateTwoArgs", msg.sender); 25 | 26 | uint256[] memory uintVals = new uint256[](1); 27 | uintVals[0] = u; 28 | int256[] memory intVals = new int256[](1); 29 | intVals[0] = i; 30 | emit FunctionArguments(uintVals, intVals); 31 | } 32 | 33 | function reverts() external { 34 | emit FunctionCalled("MockDownstream", "reverts", msg.sender); 35 | 36 | uint256[] memory uintVals = new uint256[](0); 37 | int256[] memory intVals = new int256[](0); 38 | emit FunctionArguments(uintVals, intVals); 39 | 40 | require(false, "reverted"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/mocks/MockOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "./Mock.sol"; 4 | 5 | contract MockOracle is Mock { 6 | bool private _validity = true; 7 | uint256 private _data; 8 | string public name; 9 | 10 | constructor(string memory name_) public { 11 | name = name_; 12 | } 13 | 14 | // Mock methods 15 | function getData() external returns (uint256, bool) { 16 | emit FunctionCalled(name, "getData", msg.sender); 17 | uint256[] memory uintVals = new uint256[](0); 18 | int256[] memory intVals = new int256[](0); 19 | emit FunctionArguments(uintVals, intVals); 20 | return (_data, _validity); 21 | } 22 | 23 | // Methods to mock data on the chain 24 | function storeData(uint256 data) public { 25 | _data = data; 26 | } 27 | 28 | function storeValidity(bool validity) public { 29 | _validity = validity; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/mocks/MockUFragments.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "./Mock.sol"; 4 | 5 | contract MockUFragments is Mock { 6 | uint256 private _supply; 7 | 8 | // Methods to mock data on the chain 9 | function storeSupply(uint256 supply) public { 10 | _supply = supply; 11 | } 12 | 13 | // Mock methods 14 | function rebase(uint256 epoch, int256 supplyDelta) public returns (uint256) { 15 | emit FunctionCalled("UFragments", "rebase", msg.sender); 16 | uint256[] memory uintVals = new uint256[](1); 17 | uintVals[0] = epoch; 18 | int256[] memory intVals = new int256[](1); 19 | intVals[0] = supplyDelta; 20 | emit FunctionArguments(uintVals, intVals); 21 | return uint256(int256(_supply) + int256(supplyDelta)); 22 | } 23 | 24 | function totalSupply() public view returns (uint256) { 25 | return _supply; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/mocks/MockUFragmentsPolicy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "./Mock.sol"; 4 | 5 | contract MockUFragmentsPolicy is Mock { 6 | function rebase() external { 7 | emit FunctionCalled("UFragmentsPolicy", "rebase", msg.sender); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/mocks/RebaseCallerContract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "../Orchestrator.sol"; 4 | 5 | contract RebaseCallerContract { 6 | function callRebase(address orchestrator) public returns (bool) { 7 | // Take out a flash loan. 8 | // Do something funky... 9 | Orchestrator(orchestrator).rebase(); // should fail 10 | // pay back flash loan. 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/mocks/SafeMathIntMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "./Mock.sol"; 4 | import "../lib/SafeMathInt.sol"; 5 | 6 | contract SafeMathIntMock is Mock { 7 | function mul(int256 a, int256 b) external returns (int256) { 8 | int256 result = SafeMathInt.mul(a, b); 9 | emit ReturnValueInt256(result); 10 | return result; 11 | } 12 | 13 | function div(int256 a, int256 b) external returns (int256) { 14 | int256 result = SafeMathInt.div(a, b); 15 | emit ReturnValueInt256(result); 16 | return result; 17 | } 18 | 19 | function sub(int256 a, int256 b) external returns (int256) { 20 | int256 result = SafeMathInt.sub(a, b); 21 | emit ReturnValueInt256(result); 22 | return result; 23 | } 24 | 25 | function add(int256 a, int256 b) external returns (int256) { 26 | int256 result = SafeMathInt.add(a, b); 27 | emit ReturnValueInt256(result); 28 | return result; 29 | } 30 | 31 | function abs(int256 a) external returns (int256) { 32 | int256 result = SafeMathInt.abs(a); 33 | emit ReturnValueInt256(result); 34 | return result; 35 | } 36 | 37 | function twoPower(int256 e, int256 one) external returns (int256) { 38 | int256 result = SafeMathInt.twoPower(e, one); 39 | emit ReturnValueInt256(result); 40 | return result; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/mocks/SelectMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "../lib/Select.sol"; 4 | 5 | contract Mock { 6 | event ReturnValueUInt256(uint256 val); 7 | } 8 | 9 | contract SelectMock is Mock { 10 | function computeMedian(uint256[] calldata data, uint256 size) external returns (uint256) { 11 | uint256 result = Select.computeMedian(data, size); 12 | emit ReturnValueUInt256(result); 13 | return result; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mocks/UInt256LibMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.4; 2 | 3 | import "./Mock.sol"; 4 | import "../lib/UInt256Lib.sol"; 5 | 6 | contract UInt256LibMock is Mock { 7 | function toInt256Safe(uint256 a) external returns (int256) { 8 | int256 result = UInt256Lib.toInt256Safe(a); 9 | emit ReturnValueInt256(result); 10 | return result; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from 'hardhat/config' 2 | import { Wallet } from 'ethers' 3 | 4 | import '@nomiclabs/hardhat-ethers' 5 | import '@nomiclabs/hardhat-waffle' 6 | import '@openzeppelin/hardhat-upgrades' 7 | import '@nomiclabs/hardhat-etherscan' 8 | import 'solidity-coverage' 9 | import 'hardhat-gas-reporter' 10 | 11 | // Loads env variables from .env file 12 | import * as dotenv from 'dotenv' 13 | dotenv.config() 14 | 15 | // load custom tasks 16 | require('./scripts/deploy') 17 | require('./scripts/upgrade') 18 | 19 | export default { 20 | etherscan: { 21 | apiKey: process.env.ETHERSCAN_API_KEY, 22 | }, 23 | networks: { 24 | hardhat: { 25 | initialBaseFeePerGas: 0, 26 | accounts: { 27 | mnemonic: Wallet.createRandom().mnemonic.phrase, 28 | }, 29 | }, 30 | ganache: { 31 | url: 'http://127.0.0.1:8545', 32 | }, 33 | goerli: { 34 | url: `https://eth-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_SECRET}`, 35 | accounts: { 36 | mnemonic: 37 | process.env.PROD_MNEMONIC || Wallet.createRandom().mnemonic.phrase, 38 | }, 39 | minGasPrice: 10000000000, 40 | gasMultiplier: 1.5, 41 | }, 42 | mainnet: { 43 | url: `https://mainnet.infura.io/v3/${process.env.INFURA_SECRET}`, 44 | accounts: { 45 | mnemonic: 46 | process.env.PROD_MNEMONIC || Wallet.createRandom().mnemonic.phrase, 47 | }, 48 | gasMultiplier: 1.05, 49 | }, 50 | }, 51 | solidity: { 52 | compilers: [ 53 | { 54 | version: '0.8.4', 55 | settings: { 56 | optimizer: { 57 | enabled: true, 58 | runs: 200, 59 | }, 60 | }, 61 | }, 62 | ], 63 | }, 64 | mocha: { 65 | timeout: 100000, 66 | bail: true, 67 | }, 68 | gasReporter: { 69 | currency: 'USD', 70 | enabled: process.env.REPORT_GAS ? true : false, 71 | excludeContracts: ['mocks/'], 72 | coinmarketcap: process.env.COINMARKETCAP_API_KEY, 73 | }, 74 | } as HardhatUserConfig 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uFragments", 3 | "version": "0.0.1", 4 | "description": "Ampleforth protocol smart contracts on Ethereum.", 5 | "keywords": [ 6 | "ethereum", 7 | "smart-contracts", 8 | "solidity" 9 | ], 10 | "homepage": "https://github.com/ampleforth/uFragments#readme", 11 | "bugs": { 12 | "url": "https://github.com/ampleforth/uFragments/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ampleforth/uFragments.git" 17 | }, 18 | "license": "ISC", 19 | "author": "dev-support@ampleforth.org", 20 | "scripts": { 21 | "compile": "yarn hardhat compile", 22 | "coverage": "yarn hardhat coverage --testfiles 'test/unit/*.ts'", 23 | "format": "yarn prettier --config .prettierrc --write '*.ts' '**/**/*.ts' 'contracts/**/*.sol'", 24 | "lint": "yarn format && yarn solhint 'contracts/**/*.sol'", 25 | "profile": "REPORT_GAS=true yarn hardhat test test/unit/*.ts", 26 | "test": "yarn hardhat test" 27 | }, 28 | "pre-commit": [ 29 | "format", 30 | "lint" 31 | ], 32 | "dependencies": { 33 | "@openzeppelin/contracts-upgradeable": "^4.7.3" 34 | }, 35 | "devDependencies": { 36 | "@ethersproject/abi": "^5.6.4", 37 | "@ethersproject/bytes": "^5.6.1", 38 | "@ethersproject/providers": "^5.6.8", 39 | "@nomiclabs/hardhat-ethers": "^2.1.0", 40 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 41 | "@nomiclabs/hardhat-waffle": "^2.0.3", 42 | "@openzeppelin/hardhat-upgrades": "^1.19.0", 43 | "@openzeppelin/upgrades-core": "^1.19.1", 44 | "@typechain/ethers-v5": "^10.1.0", 45 | "@typechain/hardhat": "^6.1.2", 46 | "@types/chai": "^4.3.1", 47 | "@types/mocha": "^9.1.1", 48 | "@types/node": "^18.6.1", 49 | "@typescript-eslint/eslint-plugin": "^5.0.0", 50 | "@typescript-eslint/parser": "^5.0.0", 51 | "chai": "^4.3.6", 52 | "dotenv": "^16.0.1", 53 | "eslint": "^8.20.0", 54 | "eslint-config-prettier": "^8.5.0", 55 | "eslint-config-standard": "^17.0.0", 56 | "eslint-plugin-import": "^2.26.0", 57 | "eslint-plugin-n": "^15.2.4", 58 | "eslint-plugin-node": "^11.1.0", 59 | "eslint-plugin-prettier": "^4.2.1", 60 | "eslint-plugin-promise": "^6.0.0", 61 | "ethereum-waffle": "^3.4.4", 62 | "ethers": "^5.6.9", 63 | "hardhat": "^2.22.13", 64 | "hardhat-gas-reporter": "^1.0.8", 65 | "prettier": "^2.7.1", 66 | "prettier-plugin-solidity": "^1.0.0-dev.23", 67 | "solhint": "^3.3.7", 68 | "solhint-plugin-prettier": "^0.0.5", 69 | "solidity-coverage": "^0.7.21", 70 | "stochasm": "^0.5.0", 71 | "ts-node": "^10.9.1", 72 | "typechain": "^8.1.0", 73 | "typescript": "^4.7.4" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, utils, constants } from 'ethers' 2 | import { task } from 'hardhat/config' 3 | import { Interface } from '@ethersproject/abi' 4 | import { getImplementationAddress } from '@openzeppelin/upgrades-core' 5 | 6 | import { 7 | waitFor, 8 | deployContract, 9 | deployProxy, 10 | deployExternalArtifact, 11 | verify, 12 | } from './helpers' 13 | 14 | task('deploy:amplforce:testnet', 'Deploy ampleforth contract suite for testnet') 15 | .addFlag('verify', 'The ERC-20 name of the wAMPL token') 16 | .addFlag('setupMultisig', 'Sets up multisig admin and transfers onwership') 17 | .setAction(async (args, hre) => { 18 | // HARD-CODED config values 19 | 20 | // The value of CPI which was set when v1.0.0 contracts were deployed in july 2019 21 | const INITIAL_CPI = BigNumber.from('109195000000000007392') 22 | const INITIAL_RATE = utils.parseUnits('1', 18) // 1.0 23 | 24 | // Rate oracle 25 | const RATE_REPORT_EXPIRATION_SEC = 86400 // 1 day 26 | const RATE_REPORT_DELAY_SEC = 0 27 | const RATE_MIN_PROVIDERS = 1 28 | 29 | // CPI oracle 30 | const CPI_REPORT_EXPIRATION_SEC = 7776000 // 90 days 31 | const CPI_REPORT_DELAY_SEC = 0 32 | const CPI_MIN_PROVIDERS = 1 33 | 34 | // Policy 35 | const DEVIATION_TRESHOLD = utils.parseUnits('0.002', 18) // 0.002% (ie) 0.05/24) 36 | const LOWER = utils.parseUnits('-0.005', 18) // rebaseFunctionNegativePercentageLimit 37 | const UPPER = utils.parseUnits('0.005', 18) // rebaseFunctionPositivePercentageLimit 38 | const POSITIVE_GROWTH = utils.parseUnits('31', 18) // rebaseFunctionPositiveGrowth; 39 | const NEGATIVE_GROWTH = utils.parseUnits('41', 18) // rebaseFunctionNegativeGrowth; 40 | const MIN_REBASE_INTERVAL = 1200 // 20 mins 41 | const REBASE_WINDOW_OFFSET = 0 42 | const REBASE_WINDOW_LEN = 2400 // 40 mins 43 | 44 | // AMPL 45 | const DECIMALS = 9 46 | 47 | // get signers 48 | const deployer = (await hre.ethers.getSigners())[0] 49 | const owner = await deployer.getAddress() 50 | console.log('Deployer', owner) 51 | 52 | // deploy ampl erc-20 53 | const ampl = await deployProxy( 54 | hre, 55 | 'UFragments', 56 | deployer, 57 | 'initialize(address)', 58 | [owner], 59 | ) 60 | const amplImpl = await getImplementationAddress( 61 | hre.ethers.provider, 62 | ampl.address, 63 | ) 64 | console.log('UFragments deployed to:', ampl.address) 65 | console.log('Implementation:', amplImpl) 66 | 67 | // deploy market oracle 68 | const marketOracle = await deployContract(hre, 'MedianOracle', deployer) 69 | await marketOracle.init( 70 | RATE_REPORT_EXPIRATION_SEC, 71 | RATE_REPORT_DELAY_SEC, 72 | RATE_MIN_PROVIDERS, 73 | ) 74 | console.log('Market oracle to:', marketOracle.address) 75 | 76 | // deploy cpi oracle 77 | const cpiOracle = await deployContract(hre, 'MedianOracle', deployer) 78 | await cpiOracle.init( 79 | CPI_REPORT_EXPIRATION_SEC, 80 | CPI_REPORT_DELAY_SEC, 81 | CPI_MIN_PROVIDERS, 82 | ) 83 | console.log('CPI oracle to:', cpiOracle.address) 84 | 85 | // deploy policy 86 | const policy = await deployProxy( 87 | hre, 88 | 'UFragmentsPolicy', 89 | deployer, 90 | 'initialize(address,address,uint256)', 91 | [owner, ampl.address, INITIAL_CPI.toString()], 92 | ) 93 | const policyImpl = await getImplementationAddress( 94 | hre.ethers.provider, 95 | policy.address, 96 | ) 97 | console.log('UFragmentsPolicy deployed to:', policy.address) 98 | console.log('Implementation:', policyImpl) 99 | 100 | // deploy orchestrator 101 | const orchestratorParams = [policy.address] 102 | const orchestrator = await deployContract( 103 | hre, 104 | 'Orchestrator', 105 | deployer, 106 | orchestratorParams, 107 | ) 108 | console.log('Orchestrator deployed to:', orchestrator.address) 109 | 110 | // Set references 111 | await waitFor(ampl.connect(deployer).setMonetaryPolicy(policy.address)) 112 | await waitFor( 113 | policy.connect(deployer).setMarketOracle(marketOracle.address), 114 | ) 115 | await waitFor(policy.connect(deployer).setCpiOracle(cpiOracle.address)) 116 | await waitFor( 117 | policy.connect(deployer).setOrchestrator(orchestrator.address), 118 | ) 119 | console.log('References set') 120 | 121 | // configure parameters 122 | await waitFor(policy.setDeviationThreshold(DEVIATION_TRESHOLD)) 123 | await waitFor(policy.setRebaseFunctionPositiveGrowth(POSITIVE_GROWTH)) 124 | await waitFor(policy.setRebaseFunctionNegativeGrowth(NEGATIVE_GROWTH)) 125 | await waitFor(policy.setRebaseFunctionNegativePercentageLimit(LOWER)) 126 | await waitFor(policy.setRebaseFunctionPositivePercentageLimit(UPPER)) 127 | await waitFor( 128 | policy.setRebaseTimingParameters( 129 | MIN_REBASE_INTERVAL, 130 | REBASE_WINDOW_OFFSET, 131 | REBASE_WINDOW_LEN, 132 | ), 133 | ) 134 | await waitFor(marketOracle.addProvider(owner)) 135 | await waitFor(cpiOracle.addProvider(owner)) 136 | console.log('Parameters configured') 137 | 138 | // initial rebase 139 | await waitFor(marketOracle.pushReport(INITIAL_RATE)) 140 | await waitFor(cpiOracle.pushReport(INITIAL_CPI)) 141 | await waitFor(orchestrator.rebase()) 142 | const r = await policy.globalAmpleforthEpochAndAMPLSupply() 143 | console.log( 144 | `Rebase success: ${r[0].toString()} ${utils.formatUnits( 145 | r[1].toString(), 146 | DECIMALS, 147 | )}`, 148 | ) 149 | 150 | // transferring ownership to multisig 151 | if (args.setupMultisig) { 152 | const adminWallet = await deployExternalArtifact( 153 | hre, 154 | 'MultiSigWallet', 155 | deployer, 156 | [[owner], 1], 157 | ) 158 | console.log('Admin/Provider wallet: ', adminWallet.address) 159 | await waitFor(marketOracle.addProvider(adminWallet.address)) 160 | await waitFor(cpiOracle.addProvider(adminWallet.address)) 161 | 162 | console.log('Transferring ownership to: ', adminWallet.address) 163 | await waitFor(marketOracle.transferOwnership(adminWallet.address)) 164 | await waitFor(cpiOracle.transferOwnership(adminWallet.address)) 165 | await waitFor(ampl.transferOwnership(adminWallet.address)) 166 | await waitFor(policy.transferOwnership(adminWallet.address)) 167 | await waitFor(orchestrator.transferOwnership(adminWallet.address)) 168 | } 169 | 170 | // verification 171 | if (args.verify) { 172 | console.log('Verifying contracts:') 173 | await verify(hre, marketOracle.address) 174 | await verify(hre, cpiOracle.address) 175 | await verify(hre, orchestrator.address, orchestratorParams) 176 | await verify(hre, ampl.address) 177 | await verify(hre, policy.address) 178 | await verify(hre, amplImpl) 179 | await verify(hre, policyImpl) 180 | } 181 | }) 182 | 183 | task('deploy:wampl', 'Deploy wampl contract') 184 | .addParam('ampl', 'The address to the AMPL token') 185 | .addParam('name', 'The ERC-20 name of the wAMPL token') 186 | .addParam('symbol', 'The ERC-20 symbol of the wAMPL token') 187 | .setAction(async (args, hre) => { 188 | console.log(args) 189 | 190 | // get signers 191 | const deployer = (await hre.ethers.getSigners())[0] 192 | console.log('Deployer', await deployer.getAddress()) 193 | 194 | // deploy contract 195 | const wampl = await deployContract(hre, 'WAMPL', deployer, [args.ampl]) 196 | await wampl.init(args.name, args.symbol) 197 | console.log('wAMPL deployed to:', wampl.address) 198 | 199 | // wait and verify 200 | await wampl.deployTransaction.wait(5) 201 | await verify(hre, wampl.address, [args.ampl]) 202 | }) 203 | 204 | task('deploy:oracle', 'Deploy the median oracle contract') 205 | .addParam('expiry', 'The report expiry') 206 | .addParam('delay', 'The report delay') 207 | .addParam('scalar', 'The scaling factor') 208 | .setAction(async (args, hre) => { 209 | console.log(args) 210 | 211 | // get signers 212 | const deployer = (await hre.ethers.getSigners())[0] 213 | console.log('Deployer', await deployer.getAddress()) 214 | 215 | // deploy contract 216 | const oracle = await deployContract(hre, 'MedianOracle', deployer, []) 217 | await oracle.init(args.expiry, args.delay, 1, args.scalar) 218 | console.log('Oracle deployed to:', oracle.address) 219 | 220 | // wait and verify 221 | await oracle.deployTransaction.wait(5) 222 | await verify(hre, oracle.address, []) 223 | }) 224 | -------------------------------------------------------------------------------- /scripts/helpers.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import { readFileSync } from 'fs' 3 | import { Signer } from 'ethers' 4 | import { HardhatRuntimeEnvironment } from 'hardhat/types' 5 | import { TransactionResponse } from '@ethersproject/providers' 6 | import { ContractFactory, Contract } from 'ethers' 7 | 8 | const EXTERNAL_ARTIFACTS_PATH = path.join(__dirname, '/../external-artifacts') 9 | export async function getContractFactoryFromExternalArtifacts( 10 | hre: HardhatRuntimeEnvironment, 11 | name: string, 12 | ): Promise { 13 | const artifact = JSON.parse( 14 | readFileSync(`${EXTERNAL_ARTIFACTS_PATH}/${name}.json`).toString(), 15 | ) 16 | return hre.ethers.getContractFactoryFromArtifact(artifact) 17 | } 18 | 19 | export async function sleep(sleepSec: number) { 20 | await new Promise((resolve) => setTimeout(resolve, sleepSec)) 21 | } 22 | 23 | export async function waitFor(tx: TransactionResponse) { 24 | return (await tx).wait() 25 | } 26 | 27 | export async function deployContract( 28 | hre: HardhatRuntimeEnvironment, 29 | factoryName: string, 30 | signer: Signer, 31 | params: any = [], 32 | ): Promise { 33 | const contract = await (await hre.ethers.getContractFactory(factoryName)) 34 | .connect(signer) 35 | .deploy(...params) 36 | await contract.deployed() 37 | return contract 38 | } 39 | 40 | export async function deployProxy( 41 | hre: HardhatRuntimeEnvironment, 42 | factoryName: string, 43 | signer: Signer, 44 | initializer: string, 45 | params: any = [], 46 | ): Promise { 47 | const contract = await hre.upgrades.deployProxy( 48 | (await hre.ethers.getContractFactory(factoryName)).connect(signer), 49 | params, 50 | { initializer }, 51 | ) 52 | await contract.deployed() 53 | return contract 54 | } 55 | 56 | export async function deployExternalArtifact( 57 | hre: HardhatRuntimeEnvironment, 58 | name: string, 59 | signer: Signer, 60 | params: any = [], 61 | ): Promise { 62 | const Factory = await getContractFactoryFromExternalArtifacts(hre, name) 63 | const contract = await Factory.connect(signer).deploy(...params) 64 | await contract.deployed() 65 | return contract 66 | } 67 | 68 | export async function verify( 69 | hre: HardhatRuntimeEnvironment, 70 | address: string, 71 | constructorArguments: any = [], 72 | ) { 73 | try { 74 | await hre.run('verify:verify', { address, constructorArguments }) 75 | } catch (e) { 76 | console.log('Verification failed:', e) 77 | console.log('Execute the following') 78 | console.log( 79 | `yarn hardhat run verify:verify --address ${address} --constructor-arguments "${JSON.stringify( 80 | constructorArguments, 81 | ).replace(/"/g, '\\"')}"`, 82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /scripts/upgrade.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | import * as path from 'path' 3 | 4 | import { task } from 'hardhat/config' 5 | import { getAdminAddress } from '@openzeppelin/upgrades-core' 6 | import { Interface } from '@ethersproject/abi' 7 | import { TransactionReceipt } from '@ethersproject/providers' 8 | import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/ProxyAdmin.json' 9 | 10 | import { 11 | getContractFactoryFromExternalArtifacts, 12 | verify, 13 | sleep, 14 | } from './helpers' 15 | 16 | const parseEvents = ( 17 | receipt: TransactionReceipt, 18 | contractInterface: Interface, 19 | eventName: string, 20 | ) => 21 | receipt.logs 22 | .map((log) => contractInterface.parseLog(log)) 23 | .filter((log) => log.name === eventName) 24 | 25 | task('check:admin', 'Upgrade ampleforth contracts') 26 | .addParam('address', 'proxy address') 27 | .setAction(async (args, hre) => { 28 | console.log(await getAdminAddress(hre.ethers.provider, args.address)) 29 | }) 30 | 31 | task('upgrade:ampl', 'Upgrade ampleforth contracts') 32 | .addParam('contract', 'which implementation contract to use') 33 | .addParam('address', 'which proxy address to upgrade') 34 | .addOptionalParam('multisig', 'which multisig address to use for upgrade') 35 | .setAction(async (args, hre) => { 36 | console.log(args) 37 | const upgrades = hre.upgrades as any 38 | 39 | // can only upgrade token or policy 40 | const supported = ['UFragments', 'UFragmentsPolicy'] 41 | if (!supported.includes(args.contract)) { 42 | throw new Error( 43 | `requested to upgrade ${args.contract} but only ${supported} are supported`, 44 | ) 45 | } 46 | 47 | // get signers 48 | const deployer = (await hre.ethers.getSigners())[0] 49 | console.log('Deployer', await deployer.getAddress()) 50 | 51 | if (args.multisig) { 52 | // deploy new implementation 53 | const implementation = await upgrades.prepareUpgrade( 54 | args.address, 55 | await hre.ethers.getContractFactory(args.contract), 56 | ) 57 | console.log( 58 | `New implementation for ${args.contract} deployed to`, 59 | implementation, 60 | ) 61 | 62 | // prepare upgrade transaction 63 | const admin = new hre.ethers.Contract( 64 | await getAdminAddress(hre.ethers.provider, args.address), 65 | ProxyAdmin.abi, 66 | deployer, 67 | ) 68 | const upgradeTx = await admin.populateTransaction.upgrade( 69 | args.address, 70 | implementation, 71 | ) 72 | console.log(`Upgrade transaction`, upgradeTx) 73 | 74 | // send upgrade transaction to multisig 75 | const MultisigWallet = await getContractFactoryFromExternalArtifacts( 76 | hre, 77 | 'MultiSigWallet', 78 | ) 79 | const multisig = (await MultisigWallet.attach(args.multisig)).connect( 80 | deployer, 81 | ) 82 | 83 | const receipt = await ( 84 | await multisig.submitTransaction( 85 | upgradeTx.to, 86 | upgradeTx.value, 87 | upgradeTx.data, 88 | ) 89 | ).wait() 90 | const events = parseEvents(receipt, multisig.interface, 'Submission') 91 | console.log( 92 | `Upgrade transaction submitted to multisig with transaction index`, 93 | events[0].args.transactionId, 94 | ) 95 | } else { 96 | await upgrades.upgradeProxy( 97 | args.address, 98 | await hre.ethers.getContractFactory(args.contract), 99 | ) 100 | console.log(args.contract, 'upgraded') 101 | } 102 | }) 103 | 104 | task('prep:upgrade:policy') 105 | .addParam('address', 'which proxy address to upgrade') 106 | .setAction(async function (args: TaskArguments, hre) { 107 | console.log(args) 108 | const upgrades = hre.upgrades as any 109 | 110 | const signer = (await hre.ethers.getSigners())[0] 111 | const signerAddress = await signer.getAddress() 112 | console.log('Signer', signerAddress) 113 | 114 | const newFactory = await hre.ethers.getContractFactory('UFragmentsPolicy') 115 | await upgrades.validateUpgrade(args.address, newFactory) 116 | const newImpl = await upgrades.prepareUpgrade(args.address, newFactory) 117 | console.log('New implementation at:', newImpl) 118 | 119 | console.log('Update implementation by executing the following') 120 | console.log( 121 | 'Proxy Admin', 122 | await getAdminAddress(hre.ethers.provider, args.address), 123 | ) 124 | console.log(`proxyAdmin.upgrade(${args.address}, ${newImpl})`) 125 | 126 | await sleep(15) 127 | await verify(hre, newImpl, []) 128 | }) 129 | -------------------------------------------------------------------------------- /test/simulation/supply_precision.ts: -------------------------------------------------------------------------------- 1 | /* 2 | In this script, 3 | During every iteration: 4 | * We double the total fragments supply. 5 | * We test the following guarantee: 6 | - the difference in totalSupply() before and after the rebase(+1) should be exactly 1. 7 | */ 8 | 9 | import { ethers, upgrades } from 'hardhat' 10 | import { expect } from 'chai' 11 | 12 | async function exec() { 13 | const [deployer] = await ethers.getSigners() 14 | const factory = await ethers.getContractFactory('UFragments') 15 | const uFragments = await upgrades.deployProxy( 16 | factory.connect(deployer), 17 | [await deployer.getAddress()], 18 | { 19 | initializer: 'initialize(address)', 20 | }, 21 | ) 22 | await uFragments.connect(deployer).setMonetaryPolicy(deployer.getAddress()) 23 | 24 | const endSupply = ethers.BigNumber.from(2).pow(128).sub(1) 25 | let preRebaseSupply = ethers.BigNumber.from(0), 26 | postRebaseSupply = ethers.BigNumber.from(0) 27 | 28 | let i = 0 29 | do { 30 | console.log('Iteration', i + 1) 31 | 32 | preRebaseSupply = await uFragments.totalSupply() 33 | await uFragments.connect(deployer).rebase(2 * i, 1) 34 | postRebaseSupply = await uFragments.totalSupply() 35 | console.log('Rebased by 1 AMPL') 36 | console.log('Total supply is now', postRebaseSupply.toString(), 'AMPL') 37 | 38 | console.log('Testing precision of supply') 39 | expect(postRebaseSupply.sub(preRebaseSupply).toNumber()).to.eq(1) 40 | 41 | console.log('Doubling supply') 42 | await uFragments.connect(deployer).rebase(2 * i + 1, postRebaseSupply) 43 | i++ 44 | } while ((await uFragments.totalSupply()).lt(endSupply)) 45 | } 46 | 47 | describe('Supply Precision', function () { 48 | it('should successfully run simulation', async function () { 49 | await exec() 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/simulation/transfer_precision.ts: -------------------------------------------------------------------------------- 1 | /* 2 | In this script, we generate random cycles of fragments growth and contraction 3 | and test the precision of fragments transfers 4 | During every iteration; percentageGrowth is sampled from a unifrom distribution between [-50%,250%] 5 | and the fragments total supply grows/contracts. 6 | In each cycle we test the following guarantees: 7 | - If address 'A' transfers x fragments to address 'B'. A's resulting external balance will 8 | be decreased by precisely x fragments, and B's external balance will be precisely 9 | increased by x fragments. 10 | */ 11 | 12 | import { ethers, upgrades } from 'hardhat' 13 | import { expect } from 'chai' 14 | import { BigNumber, BigNumberish, Contract, Signer } from 'ethers' 15 | import { imul } from '../utils/utils' 16 | const Stochasm = require('stochasm') 17 | 18 | const endSupply = ethers.BigNumber.from(2).pow(128).sub(1) 19 | const uFragmentsGrowth = new Stochasm({ 20 | min: -0.5, 21 | max: 2.5, 22 | seed: 'fragments.org', 23 | }) 24 | 25 | let uFragments: Contract, 26 | inflation: BigNumber, 27 | rebaseAmt = ethers.BigNumber.from(0), 28 | preRebaseSupply = ethers.BigNumber.from(0), 29 | postRebaseSupply = ethers.BigNumber.from(0) 30 | 31 | async function checkBalancesAfterOperation( 32 | users: Signer[], 33 | op: Function, 34 | chk: Function, 35 | ) { 36 | const _bals = [] 37 | const bals = [] 38 | let u 39 | for (u in users) { 40 | if (Object.prototype.hasOwnProperty.call(users, u)) { 41 | _bals.push(await uFragments.balanceOf(users[u].getAddress())) 42 | } 43 | } 44 | await op() 45 | for (u in users) { 46 | if (Object.prototype.hasOwnProperty.call(users, u)) { 47 | bals.push(await uFragments.balanceOf(users[u].getAddress())) 48 | } 49 | } 50 | chk(_bals, bals) 51 | } 52 | 53 | async function checkBalancesAfterTransfer(users: Signer[], tAmt: BigNumberish) { 54 | await checkBalancesAfterOperation( 55 | users, 56 | async function () { 57 | await uFragments.connect(users[0]).transfer(users[1].getAddress(), tAmt) 58 | }, 59 | function ([_u0Bal, _u1Bal]: BigNumber[], [u0Bal, u1Bal]: BigNumber[]) { 60 | const _sum = _u0Bal.add(_u1Bal) 61 | const sum = u0Bal.add(u1Bal) 62 | expect(_sum).to.eq(sum) 63 | expect(_u0Bal.sub(tAmt)).to.eq(u0Bal) 64 | expect(_u1Bal.add(tAmt)).to.eq(u1Bal) 65 | }, 66 | ) 67 | } 68 | 69 | async function exec() { 70 | const [deployer, user] = await ethers.getSigners() 71 | const factory = await ethers.getContractFactory('UFragments') 72 | uFragments = await upgrades.deployProxy( 73 | factory.connect(deployer), 74 | [await deployer.getAddress()], 75 | { 76 | initializer: 'initialize(address)', 77 | }, 78 | ) 79 | await uFragments.connect(deployer).setMonetaryPolicy(deployer.getAddress()) 80 | 81 | let i = 0 82 | do { 83 | await uFragments.connect(deployer).rebase(i + 1, rebaseAmt) 84 | postRebaseSupply = await uFragments.totalSupply() 85 | i++ 86 | 87 | console.log('Rebased iteration', i) 88 | console.log('Rebased by', rebaseAmt.toString(), 'AMPL') 89 | console.log('Total supply is now', postRebaseSupply.toString(), 'AMPL') 90 | 91 | console.log('Testing precision of 1c transfer') 92 | await checkBalancesAfterTransfer([deployer, user], 1) 93 | await checkBalancesAfterTransfer([user, deployer], 1) 94 | 95 | console.log('Testing precision of max denomination') 96 | const tAmt = await uFragments.balanceOf(deployer.getAddress()) 97 | await checkBalancesAfterTransfer([deployer, user], tAmt) 98 | await checkBalancesAfterTransfer([user, deployer], tAmt) 99 | 100 | preRebaseSupply = await uFragments.totalSupply() 101 | inflation = uFragmentsGrowth.next().toFixed(5) 102 | rebaseAmt = imul(preRebaseSupply, inflation, 1) 103 | } while ((await uFragments.totalSupply()).add(rebaseAmt).lt(endSupply)) 104 | } 105 | 106 | describe('Transfer Precision', function () { 107 | it('should successfully run simulation', async function () { 108 | await exec() 109 | }) 110 | }) 111 | -------------------------------------------------------------------------------- /test/unit/Orchestrator.ts: -------------------------------------------------------------------------------- 1 | import { ethers, waffle } from 'hardhat' 2 | import { Contract, Signer } from 'ethers' 3 | import { increaseTime } from '../utils/utils' 4 | import { expect } from 'chai' 5 | import { TransactionResponse } from '@ethersproject/providers' 6 | 7 | let orchestrator: Contract, mockPolicy: Contract, mockDownstream: Contract 8 | let r: Promise 9 | let deployer: Signer, user: Signer 10 | 11 | async function mockedOrchestrator() { 12 | await increaseTime(86400) 13 | // get signers 14 | const [deployer, user] = await ethers.getSigners() 15 | // deploy mocks 16 | const mockPolicy = await ( 17 | await ethers.getContractFactory('MockUFragmentsPolicy') 18 | ) 19 | .connect(deployer) 20 | .deploy() 21 | const orchestrator = await (await ethers.getContractFactory('Orchestrator')) 22 | .connect(deployer) 23 | .deploy(mockPolicy.address) 24 | const mockDownstream = await ( 25 | await ethers.getContractFactory('MockDownstream') 26 | ) 27 | .connect(deployer) 28 | .deploy() 29 | return { 30 | deployer, 31 | user, 32 | orchestrator, 33 | mockPolicy, 34 | mockDownstream, 35 | } 36 | } 37 | 38 | describe('Orchestrator', function () { 39 | before('setup Orchestrator contract', async () => { 40 | ;({ deployer, user, orchestrator, mockPolicy, mockDownstream } = 41 | await waffle.loadFixture(mockedOrchestrator)) 42 | }) 43 | 44 | describe('when sent ether', async function () { 45 | it('should reject', async function () { 46 | await expect(user.sendTransaction({ to: orchestrator.address, value: 1 })) 47 | .to.be.reverted 48 | }) 49 | }) 50 | 51 | describe('when rebase called by a contract', function () { 52 | it('should fail', async function () { 53 | const rebaseCallerContract = await ( 54 | await ethers.getContractFactory('RebaseCallerContract') 55 | ) 56 | .connect(deployer) 57 | .deploy() 58 | await expect(rebaseCallerContract.callRebase(orchestrator.address)).to.be 59 | .reverted 60 | }) 61 | }) 62 | 63 | describe('when rebase called by a contract which is being constructed', function () { 64 | it('should fail', async function () { 65 | await expect( 66 | (await ethers.getContractFactory('ConstructorRebaseCallerContract')) 67 | .connect(deployer) 68 | .deploy(orchestrator.address), 69 | ).to.be.reverted 70 | }) 71 | }) 72 | 73 | describe('when transaction list is empty', async function () { 74 | before('calling rebase', async function () { 75 | r = orchestrator.rebase() 76 | }) 77 | 78 | it('should have no transactions', async function () { 79 | expect(await orchestrator.transactionsSize()).to.eq(0) 80 | }) 81 | 82 | it('should call rebase on policy', async function () { 83 | await expect(r) 84 | .to.emit(mockPolicy, 'FunctionCalled') 85 | .withArgs('UFragmentsPolicy', 'rebase', orchestrator.address) 86 | }) 87 | 88 | it('should not have any subsequent logs', async function () { 89 | expect((await (await r).wait()).logs.length).to.eq(1) 90 | }) 91 | }) 92 | 93 | describe('when there is a single transaction', async function () { 94 | before('adding a transaction', async function () { 95 | const updateOneArgEncoded = 96 | await mockDownstream.populateTransaction.updateOneArg(12345) 97 | await orchestrator 98 | .connect(deployer) 99 | .addTransaction(mockDownstream.address, updateOneArgEncoded.data) 100 | r = orchestrator.connect(deployer).rebase() 101 | }) 102 | 103 | it('should have 1 transaction', async function () { 104 | expect(await orchestrator.transactionsSize()).to.eq(1) 105 | }) 106 | 107 | it('should call rebase on policy', async function () { 108 | await expect(r) 109 | .to.emit(mockPolicy, 'FunctionCalled') 110 | .withArgs('UFragmentsPolicy', 'rebase', orchestrator.address) 111 | }) 112 | 113 | it('should call the transaction', async function () { 114 | await expect(r) 115 | .to.emit(mockDownstream, 'FunctionCalled') 116 | .withArgs('MockDownstream', 'updateOneArg', orchestrator.address) 117 | 118 | await expect(r) 119 | .to.emit(mockDownstream, 'FunctionArguments') 120 | .withArgs([12345], []) 121 | }) 122 | 123 | it('should not have any subsequent logs', async function () { 124 | expect((await (await r).wait()).logs.length).to.eq(3) 125 | }) 126 | }) 127 | 128 | describe('when there are two transactions', async function () { 129 | before('adding a transaction', async function () { 130 | const updateTwoArgsEncoded = 131 | await mockDownstream.populateTransaction.updateTwoArgs(12345, 23456) 132 | await orchestrator 133 | .connect(deployer) 134 | .addTransaction(mockDownstream.address, updateTwoArgsEncoded.data) 135 | r = orchestrator.connect(deployer).rebase() 136 | }) 137 | 138 | it('should have 2 transactions', async function () { 139 | expect(await orchestrator.transactionsSize()).to.eq(2) 140 | }) 141 | 142 | it('should call rebase on policy', async function () { 143 | await expect(r) 144 | .to.emit(mockPolicy, 'FunctionCalled') 145 | .withArgs('UFragmentsPolicy', 'rebase', orchestrator.address) 146 | }) 147 | 148 | it('should call first transaction', async function () { 149 | await expect(r) 150 | .to.emit(mockDownstream, 'FunctionCalled') 151 | .withArgs('MockDownstream', 'updateOneArg', orchestrator.address) 152 | 153 | await expect(r) 154 | .to.emit(mockDownstream, 'FunctionArguments') 155 | .withArgs([12345], []) 156 | }) 157 | 158 | it('should call second transaction', async function () { 159 | await expect(r) 160 | .to.emit(mockDownstream, 'FunctionCalled') 161 | .withArgs('MockDownstream', 'updateTwoArgs', orchestrator.address) 162 | 163 | await expect(r) 164 | .to.emit(mockDownstream, 'FunctionArguments') 165 | .withArgs([12345], [23456]) 166 | }) 167 | 168 | it('should not have any subsequent logs', async function () { 169 | expect((await (await r).wait()).logs.length).to.eq(5) 170 | }) 171 | }) 172 | 173 | describe('when 1st transaction is disabled', async function () { 174 | before('disabling a transaction', async function () { 175 | await orchestrator.connect(deployer).setTransactionEnabled(0, false) 176 | r = orchestrator.connect(deployer).rebase() 177 | }) 178 | 179 | it('should have 2 transactions', async function () { 180 | expect(await orchestrator.transactionsSize()).to.eq(2) 181 | }) 182 | 183 | it('should call rebase on policy', async function () { 184 | await expect(r) 185 | .to.emit(mockPolicy, 'FunctionCalled') 186 | .withArgs('UFragmentsPolicy', 'rebase', orchestrator.address) 187 | }) 188 | 189 | it('should call second transaction', async function () { 190 | await expect(r) 191 | .to.emit(mockDownstream, 'FunctionCalled') 192 | .withArgs('MockDownstream', 'updateTwoArgs', orchestrator.address) 193 | 194 | await expect(r) 195 | .to.emit(mockDownstream, 'FunctionArguments') 196 | .withArgs([12345], [23456]) 197 | }) 198 | 199 | it('should not have any subsequent logs', async function () { 200 | expect(await (await (await r).wait()).logs.length).to.eq(3) 201 | }) 202 | }) 203 | 204 | describe('when a transaction is removed', async function () { 205 | before('removing 1st transaction', async function () { 206 | await orchestrator.connect(deployer).removeTransaction(0) 207 | r = orchestrator.connect(deployer).rebase() 208 | }) 209 | 210 | it('should have 1 transaction', async function () { 211 | expect(await orchestrator.transactionsSize()).to.eq(1) 212 | }) 213 | 214 | it('should call rebase on policy', async function () { 215 | await expect(r) 216 | .to.emit(mockPolicy, 'FunctionCalled') 217 | .withArgs('UFragmentsPolicy', 'rebase', orchestrator.address) 218 | }) 219 | 220 | it('should call the transaction', async function () { 221 | await expect(r) 222 | .to.emit(mockDownstream, 'FunctionCalled') 223 | .withArgs('MockDownstream', 'updateTwoArgs', orchestrator.address) 224 | 225 | await expect(r) 226 | .to.emit(mockDownstream, 'FunctionArguments') 227 | .withArgs([12345], [23456]) 228 | }) 229 | 230 | it('should not have any subsequent logs', async function () { 231 | expect((await (await r).wait()).logs.length).to.eq(3) 232 | }) 233 | }) 234 | 235 | describe('when all transactions are removed', async function () { 236 | before('removing 1st transaction', async function () { 237 | await orchestrator.connect(deployer).removeTransaction(0) 238 | r = orchestrator.connect(deployer).rebase() 239 | }) 240 | 241 | it('should have 0 transactions', async function () { 242 | expect(await orchestrator.transactionsSize()).to.eq(0) 243 | }) 244 | 245 | it('should call rebase on policy', async function () { 246 | await expect(r) 247 | .to.emit(mockPolicy, 'FunctionCalled') 248 | .withArgs('UFragmentsPolicy', 'rebase', orchestrator.address) 249 | }) 250 | 251 | it('should not have any subsequent logs', async function () { 252 | expect((await (await r).wait()).logs.length).to.eq(1) 253 | }) 254 | }) 255 | 256 | describe('when a transaction reverts', async function () { 257 | before('adding 3 transactions', async function () { 258 | const updateOneArgEncoded = 259 | await mockDownstream.populateTransaction.updateOneArg(123) 260 | await orchestrator 261 | .connect(deployer) 262 | .addTransaction(mockDownstream.address, updateOneArgEncoded.data) 263 | 264 | const revertsEncoded = await mockDownstream.populateTransaction.reverts() 265 | await orchestrator 266 | .connect(deployer) 267 | .addTransaction(mockDownstream.address, revertsEncoded.data) 268 | 269 | const updateTwoArgsEncoded = 270 | await mockDownstream.populateTransaction.updateTwoArgs(12345, 23456) 271 | await orchestrator 272 | .connect(deployer) 273 | .addTransaction(mockDownstream.address, updateTwoArgsEncoded.data) 274 | await expect(orchestrator.connect(deployer).rebase()).to.be.reverted 275 | }) 276 | 277 | it('should have 3 transactions', async function () { 278 | expect(await orchestrator.transactionsSize()).to.eq(3) 279 | }) 280 | }) 281 | 282 | describe('Access Control', function () { 283 | describe('addTransaction', async function () { 284 | it('should be callable by owner', async function () { 285 | const updateNoArgEncoded = 286 | await mockDownstream.populateTransaction.updateNoArg() 287 | await expect( 288 | orchestrator 289 | .connect(deployer) 290 | .addTransaction(mockDownstream.address, updateNoArgEncoded.data), 291 | ).to.not.be.reverted 292 | }) 293 | 294 | it('should not be callable by others', async function () { 295 | const updateNoArgEncoded = 296 | await mockDownstream.populateTransaction.updateNoArg() 297 | await expect( 298 | orchestrator 299 | .connect(user) 300 | .addTransaction(mockDownstream.address, updateNoArgEncoded.data), 301 | ).to.be.reverted 302 | }) 303 | }) 304 | 305 | describe('setTransactionEnabled', async function () { 306 | it('should be callable by owner', async function () { 307 | expect(await orchestrator.transactionsSize()).to.gt(0) 308 | await expect( 309 | orchestrator.connect(deployer).setTransactionEnabled(0, true), 310 | ).to.not.be.reverted 311 | }) 312 | 313 | it('should revert if index out of bounds', async function () { 314 | expect(await orchestrator.transactionsSize()).to.lt(5) 315 | await expect( 316 | orchestrator.connect(deployer).setTransactionEnabled(5, true), 317 | ).to.be.reverted 318 | }) 319 | 320 | it('should not be callable by others', async function () { 321 | expect(await orchestrator.transactionsSize()).to.gt(0) 322 | await expect(orchestrator.connect(user).setTransactionEnabled(0, true)) 323 | .to.be.reverted 324 | }) 325 | }) 326 | 327 | describe('removeTransaction', async function () { 328 | it('should not be callable by others', async function () { 329 | expect(await orchestrator.transactionsSize()).to.gt(0) 330 | await expect(orchestrator.connect(user).removeTransaction(0)).to.be 331 | .reverted 332 | }) 333 | 334 | it('should revert if index out of bounds', async function () { 335 | expect(await orchestrator.transactionsSize()).to.lt(5) 336 | await expect(orchestrator.connect(deployer).removeTransaction(5)).to.be 337 | .reverted 338 | }) 339 | 340 | it('should be callable by owner', async function () { 341 | expect(await orchestrator.transactionsSize()).to.gt(0) 342 | await expect(orchestrator.connect(deployer).removeTransaction(0)).to.not 343 | .be.reverted 344 | }) 345 | }) 346 | 347 | describe('transferOwnership', async function () { 348 | it('should transfer ownership', async function () { 349 | expect(await orchestrator.owner()).to.eq(await deployer.getAddress()) 350 | await orchestrator 351 | .connect(deployer) 352 | .transferOwnership(user.getAddress()) 353 | expect(await orchestrator.owner()).to.eq(await user.getAddress()) 354 | }) 355 | }) 356 | }) 357 | }) 358 | -------------------------------------------------------------------------------- /test/unit/SafeMathInt.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | import { Contract } from 'ethers' 3 | import { expect } from 'chai' 4 | 5 | describe('SafeMathInt', () => { 6 | const MIN_INT256 = ethers.BigNumber.from(-2).pow(255) 7 | const MAX_INT256 = ethers.BigNumber.from(2).pow(255).sub(1) 8 | 9 | let safeMathInt: Contract 10 | 11 | beforeEach(async function () { 12 | // deploy contract 13 | const factory = await ethers.getContractFactory('SafeMathIntMock') 14 | safeMathInt = await factory.deploy() 15 | await safeMathInt.deployed() 16 | }) 17 | 18 | describe('add', function () { 19 | it('adds correctly', async function () { 20 | const a = ethers.BigNumber.from(5678) 21 | const b = ethers.BigNumber.from(1234) 22 | 23 | await expect(safeMathInt.add(a, b)) 24 | .to.emit(safeMathInt, 'ReturnValueInt256') 25 | .withArgs(a.add(b)) 26 | }) 27 | 28 | it('should fail on addition overflow', async function () { 29 | const a = MAX_INT256 30 | const b = ethers.BigNumber.from(1) 31 | 32 | await expect(safeMathInt.add(a, b)).to.be.reverted 33 | await expect(safeMathInt.add(b, a)).to.be.reverted 34 | }) 35 | 36 | it('should fail on addition overflow, swapped args', async function () { 37 | const a = ethers.BigNumber.from(1) 38 | const b = MAX_INT256 39 | 40 | await expect(safeMathInt.add(a, b)).to.be.reverted 41 | await expect(safeMathInt.add(b, a)).to.be.reverted 42 | }) 43 | 44 | it('should fail on addition negative overflow', async function () { 45 | const a = MIN_INT256 46 | const b = ethers.BigNumber.from(-1) 47 | 48 | await expect(safeMathInt.add(a, b)).to.be.reverted 49 | await expect(safeMathInt.add(b, a)).to.be.reverted 50 | }) 51 | }) 52 | 53 | describe('sub', function () { 54 | it('subtracts correctly', async function () { 55 | const a = ethers.BigNumber.from(5678) 56 | const b = ethers.BigNumber.from(1234) 57 | 58 | await expect(safeMathInt.sub(a, b)) 59 | .to.emit(safeMathInt, 'ReturnValueInt256') 60 | .withArgs(a.sub(b)) 61 | }) 62 | 63 | it('should fail on subtraction overflow', async function () { 64 | const a = MAX_INT256 65 | const b = ethers.BigNumber.from(-1) 66 | 67 | await expect(safeMathInt.sub(a, b)).to.be.reverted 68 | }) 69 | 70 | it('should fail on subtraction negative overflow', async function () { 71 | const a = MIN_INT256 72 | const b = ethers.BigNumber.from(1) 73 | 74 | await expect(safeMathInt.sub(a, b)).to.be.reverted 75 | }) 76 | }) 77 | 78 | describe('mul', function () { 79 | it('multiplies correctly', async function () { 80 | const a = ethers.BigNumber.from(1234) 81 | const b = ethers.BigNumber.from(5678) 82 | 83 | await expect(safeMathInt.mul(a, b)) 84 | .to.emit(safeMathInt, 'ReturnValueInt256') 85 | .withArgs(a.mul(b)) 86 | }) 87 | 88 | it('handles a zero product correctly', async function () { 89 | const a = ethers.BigNumber.from(0) 90 | const b = ethers.BigNumber.from(5678) 91 | 92 | await expect(safeMathInt.mul(a, b)) 93 | .to.emit(safeMathInt, 'ReturnValueInt256') 94 | .withArgs(a.mul(b)) 95 | }) 96 | 97 | it('should fail on multiplication overflow', async function () { 98 | const a = MAX_INT256 99 | const b = ethers.BigNumber.from(2) 100 | 101 | await expect(safeMathInt.mul(a, b)).to.be.reverted 102 | await expect(safeMathInt.mul(b, a)).to.be.reverted 103 | }) 104 | 105 | it('should fail on multiplication negative overflow', async function () { 106 | const a = MIN_INT256 107 | const b = ethers.BigNumber.from(2) 108 | 109 | await expect(safeMathInt.mul(a, b)).to.be.reverted 110 | await expect(safeMathInt.mul(b, a)).to.be.reverted 111 | }) 112 | 113 | it('should fail on multiplication between -1 and MIN_INT256', async function () { 114 | const a = MIN_INT256 115 | const b = ethers.BigNumber.from(-1) 116 | 117 | await expect(safeMathInt.mul(a, b)).to.be.reverted 118 | await expect(safeMathInt.mul(b, a)).to.be.reverted 119 | }) 120 | }) 121 | 122 | describe('div', function () { 123 | it('divides correctly', async function () { 124 | const a = ethers.BigNumber.from(5678) 125 | const b = ethers.BigNumber.from(5678) 126 | 127 | await expect(safeMathInt.div(a, b)) 128 | .to.emit(safeMathInt, 'ReturnValueInt256') 129 | .withArgs(a.div(b)) 130 | }) 131 | 132 | it('should fail on zero division', async function () { 133 | const a = ethers.BigNumber.from(5678) 134 | const b = ethers.BigNumber.from(0) 135 | 136 | await expect(safeMathInt.div(a, b)).to.be.reverted 137 | }) 138 | 139 | it('should fail when MIN_INT256 is divided by -1', async function () { 140 | const a = ethers.BigNumber.from(MIN_INT256) 141 | const b = ethers.BigNumber.from(-1) 142 | 143 | await expect(safeMathInt.div(a, b)).to.be.reverted 144 | }) 145 | }) 146 | 147 | describe('abs', function () { 148 | it('works for 0', async function () { 149 | await expect(safeMathInt.abs(0)) 150 | .to.emit(safeMathInt, 'ReturnValueInt256') 151 | .withArgs(0) 152 | }) 153 | 154 | it('works on positive numbers', async function () { 155 | await expect(safeMathInt.abs(100)) 156 | .to.emit(safeMathInt, 'ReturnValueInt256') 157 | .withArgs(100) 158 | }) 159 | 160 | it('works on negative numbers', async function () { 161 | await expect(safeMathInt.abs(-100)) 162 | .to.emit(safeMathInt, 'ReturnValueInt256') 163 | .withArgs(100) 164 | }) 165 | 166 | it('fails on overflow condition', async function () { 167 | await expect(safeMathInt.abs(MIN_INT256)).to.be.reverted 168 | }) 169 | }) 170 | describe('twoPower', function () { 171 | const decimals18 = ethers.BigNumber.from('1000000000000000000') 172 | const decimals10 = ethers.BigNumber.from('10000000000') 173 | it('2^0', async function () { 174 | const e = ethers.BigNumber.from(0) 175 | const one = ethers.BigNumber.from(1).mul(decimals18) 176 | await expect(safeMathInt.twoPower(e, one)) 177 | .to.emit(safeMathInt, 'ReturnValueInt256') 178 | .withArgs(one) 179 | }) 180 | it('2^1', async function () { 181 | const e = ethers.BigNumber.from(1).mul(decimals18) 182 | const one = ethers.BigNumber.from(1).mul(decimals18) 183 | const result = ethers.BigNumber.from(2).mul(decimals18) 184 | ;(await expect(safeMathInt.twoPower(e, one))).to 185 | .emit(safeMathInt, 'ReturnValueInt256') 186 | .withArgs(result) 187 | }) 188 | it('2^30', async function () { 189 | const e = ethers.BigNumber.from(30).mul(decimals18) 190 | const one = ethers.BigNumber.from(1).mul(decimals18) 191 | const result = ethers.BigNumber.from(2 ** 30).mul(decimals18) 192 | ;(await expect(safeMathInt.twoPower(e, one))).to 193 | .emit(safeMathInt, 'ReturnValueInt256') 194 | .withArgs(result) 195 | }) 196 | it('2^2.5', async function () { 197 | const e = ethers.BigNumber.from('25000000000') 198 | const one = ethers.BigNumber.from(1).mul(decimals10) 199 | const result = ethers.BigNumber.from('56568542494') 200 | ;(await expect(safeMathInt.twoPower(e, one))).to 201 | .emit(safeMathInt, 'ReturnValueInt256') 202 | .withArgs(result) 203 | }) 204 | it('2^2.25', async function () { 205 | const e = ethers.BigNumber.from('22500000000') 206 | const one = ethers.BigNumber.from(1).mul(decimals10) 207 | const result = ethers.BigNumber.from('47568284600') 208 | ;(await expect(safeMathInt.twoPower(e, one))).to 209 | .emit(safeMathInt, 'ReturnValueInt256') 210 | .withArgs(result) 211 | }) 212 | it('2^-2.25', async function () { 213 | const e = ethers.BigNumber.from('-22500000000') 214 | const one = ethers.BigNumber.from(1).mul(decimals10) 215 | const result = ethers.BigNumber.from('2102241038') 216 | ;(await expect(safeMathInt.twoPower(e, one))).to 217 | .emit(safeMathInt, 'ReturnValueInt256') 218 | .withArgs(result) 219 | }) 220 | it('2^-0.6', async function () { 221 | const e = ethers.BigNumber.from('-6000000000') 222 | const one = ethers.BigNumber.from(1).mul(decimals10) 223 | const result = ethers.BigNumber.from('6626183216') 224 | ;(await expect(safeMathInt.twoPower(e, one))).to 225 | .emit(safeMathInt, 'ReturnValueInt256') 226 | .withArgs(result) 227 | }) 228 | it('2^2.96875', async function () { 229 | const e = ethers.BigNumber.from('29687500000') 230 | const one = ethers.BigNumber.from(1).mul(decimals10) 231 | const result = ethers.BigNumber.from('78285764964') 232 | ;(await expect(safeMathInt.twoPower(e, one))).to 233 | .emit(safeMathInt, 'ReturnValueInt256') 234 | .withArgs(result) 235 | }) 236 | it('2^2.99', async function () { 237 | const e = ethers.BigNumber.from('29900000000') 238 | const one = ethers.BigNumber.from(1).mul(decimals10) 239 | const result = ethers.BigNumber.from('78285764964') 240 | ;(await expect(safeMathInt.twoPower(e, one))).to 241 | .emit(safeMathInt, 'ReturnValueInt256') 242 | .withArgs(result) 243 | }) 244 | it('should fail on too small exponents', async function () { 245 | const e = ethers.BigNumber.from('-1011000000000') 246 | const one = ethers.BigNumber.from(1).mul(decimals10) 247 | await expect(safeMathInt.twoPower(e, one)).to.be.reverted 248 | }) 249 | it('should fail on too large exponents', async function () { 250 | const e = ethers.BigNumber.from('1011000000000') 251 | const one = ethers.BigNumber.from(1).mul(decimals10) 252 | await expect(safeMathInt.twoPower(e, one)).to.be.reverted 253 | }) 254 | }) 255 | }) 256 | -------------------------------------------------------------------------------- /test/unit/Select.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | import { Contract } from 'ethers' 3 | import { expect } from 'chai' 4 | 5 | describe('Select', () => { 6 | let select: Contract 7 | 8 | beforeEach(async function () { 9 | const factory = await ethers.getContractFactory('SelectMock') 10 | select = await factory.deploy() 11 | await select.deployed() 12 | }) 13 | 14 | describe('computeMedian', function () { 15 | it('median of 1', async function () { 16 | const a = ethers.BigNumber.from(5678) 17 | await expect(select.computeMedian([a], 1)) 18 | .to.emit(select, 'ReturnValueUInt256') 19 | .withArgs(a) 20 | }) 21 | 22 | it('median of 2', async function () { 23 | const list = [ethers.BigNumber.from(10000), ethers.BigNumber.from(30000)] 24 | await expect(select.computeMedian(list, 2)) 25 | .to.emit(select, 'ReturnValueUInt256') 26 | .withArgs(20000) 27 | }) 28 | 29 | it('median of 3', async function () { 30 | const list = [ 31 | ethers.BigNumber.from(10000), 32 | ethers.BigNumber.from(30000), 33 | ethers.BigNumber.from(21000), 34 | ] 35 | await expect(select.computeMedian(list, 3)) 36 | .to.emit(select, 'ReturnValueUInt256') 37 | .withArgs(21000) 38 | }) 39 | 40 | it('median of odd sized list', async function () { 41 | const count = 15 42 | const list = Array.from({ length: count }, () => 43 | Math.floor(Math.random() * 10 ** 18), 44 | ) 45 | const median = ethers.BigNumber.from( 46 | [...list].sort((a, b) => b - a)[Math.floor(count / 2)].toString(), 47 | ) 48 | const bn_list = Array.from(list, (x) => 49 | ethers.BigNumber.from(x.toString()), 50 | ) 51 | await expect(select.computeMedian(bn_list, count)) 52 | .to.emit(select, 'ReturnValueUInt256') 53 | .withArgs(median) 54 | }) 55 | 56 | it('median of even sized list', async function () { 57 | const count = 20 58 | const list = Array.from({ length: count }, () => 59 | Math.floor(Math.random() * 10 ** 18), 60 | ) 61 | const bn_list = Array.from(list, (x) => 62 | ethers.BigNumber.from(x.toString()), 63 | ) 64 | list.sort((a, b) => b - a) 65 | let median = ethers.BigNumber.from(list[Math.floor(count / 2)].toString()) 66 | median = median.add( 67 | ethers.BigNumber.from(list[Math.floor(count / 2) - 1].toString()), 68 | ) 69 | median = median.div(2) 70 | 71 | await expect(select.computeMedian(bn_list, count)) 72 | .to.emit(select, 'ReturnValueUInt256') 73 | .withArgs(median) 74 | }) 75 | 76 | it('not enough elements in array', async function () { 77 | await expect(select.computeMedian([1], 2)).to.be.reverted 78 | }) 79 | 80 | it('median of empty list', async function () { 81 | await expect(select.computeMedian([], 1)).to.be.reverted 82 | }) 83 | 84 | it('median of list of size 0', async function () { 85 | await expect(select.computeMedian([10000], 0)).to.be.reverted 86 | }) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /test/unit/UFragments.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from 'hardhat' 2 | import { Contract, Signer, BigNumber } from 'ethers' 3 | import { expect } from 'chai' 4 | 5 | const toUFrgDenomination = (ample: string): BigNumber => 6 | ethers.utils.parseUnits(ample, DECIMALS) 7 | 8 | const DECIMALS = 9 9 | const INITIAL_SUPPLY = ethers.utils.parseUnits('50', 6 + DECIMALS) 10 | const MAX_UINT256 = ethers.BigNumber.from(2).pow(256).sub(1) 11 | const MAX_INT256 = ethers.BigNumber.from(2).pow(255).sub(1) 12 | const TOTAL_GONS = MAX_UINT256.sub(MAX_UINT256.mod(INITIAL_SUPPLY)) 13 | 14 | const transferAmount = toUFrgDenomination('10') 15 | const unitTokenAmount = toUFrgDenomination('1') 16 | 17 | let accounts: Signer[], 18 | deployer: Signer, 19 | uFragments: Contract, 20 | initialSupply: BigNumber 21 | 22 | async function setupContracts() { 23 | // prepare signers 24 | accounts = await ethers.getSigners() 25 | deployer = accounts[0] 26 | // deploy upgradable token 27 | const factory = await ethers.getContractFactory('UFragments') 28 | uFragments = await upgrades.deployProxy( 29 | factory, 30 | [await deployer.getAddress()], 31 | { 32 | initializer: 'initialize(address)', 33 | }, 34 | ) 35 | // fetch initial supply 36 | initialSupply = await uFragments.totalSupply() 37 | } 38 | 39 | describe('UFragments', () => { 40 | before('setup UFragments contract', setupContracts) 41 | 42 | it('should reject any ether sent to it', async function () { 43 | const user = accounts[1] 44 | await expect(user.sendTransaction({ to: uFragments.address, value: 1 })).to 45 | .be.reverted 46 | }) 47 | }) 48 | 49 | describe('UFragments:Initialization', () => { 50 | before('setup UFragments contract', setupContracts) 51 | 52 | it('should transfer 50M uFragments to the deployer', async function () { 53 | expect(await uFragments.balanceOf(await deployer.getAddress())).to.eq( 54 | INITIAL_SUPPLY, 55 | ) 56 | }) 57 | 58 | it('should set the totalSupply to 50M', async function () { 59 | expect(await uFragments.totalSupply()).to.eq(INITIAL_SUPPLY) 60 | }) 61 | 62 | it('should set the owner', async function () { 63 | expect(await uFragments.owner()).to.eq(await deployer.getAddress()) 64 | }) 65 | 66 | it('should set detailed ERC20 parameters', async function () { 67 | expect(await uFragments.name()).to.eq('Ampleforth') 68 | expect(await uFragments.symbol()).to.eq('AMPL') 69 | expect(await uFragments.decimals()).to.eq(DECIMALS) 70 | }) 71 | }) 72 | 73 | describe('UFragments:setMonetaryPolicy', async () => { 74 | let policy: Signer, policyAddress: string 75 | 76 | before('setup UFragments contract', async () => { 77 | await setupContracts() 78 | policy = accounts[1] 79 | policyAddress = await policy.getAddress() 80 | }) 81 | 82 | it('should set reference to policy contract', async function () { 83 | await expect(uFragments.connect(deployer).setMonetaryPolicy(policyAddress)) 84 | .to.emit(uFragments, 'LogMonetaryPolicyUpdated') 85 | .withArgs(policyAddress) 86 | expect(await uFragments.monetaryPolicy()).to.eq(policyAddress) 87 | }) 88 | }) 89 | 90 | describe('UFragments:setMonetaryPolicy:accessControl', async () => { 91 | let policy: Signer, policyAddress: string 92 | 93 | before('setup UFragments contract', async () => { 94 | await setupContracts() 95 | policy = accounts[1] 96 | policyAddress = await policy.getAddress() 97 | }) 98 | 99 | it('should be callable by owner', async function () { 100 | await expect(uFragments.connect(deployer).setMonetaryPolicy(policyAddress)) 101 | .to.not.be.reverted 102 | }) 103 | }) 104 | 105 | describe('UFragments:setMonetaryPolicy:accessControl', async () => { 106 | let policy: Signer, policyAddress: string, user: Signer 107 | 108 | before('setup UFragments contract', async () => { 109 | await setupContracts() 110 | policy = accounts[1] 111 | user = accounts[2] 112 | policyAddress = await policy.getAddress() 113 | }) 114 | 115 | it('should NOT be callable by non-owner', async function () { 116 | await expect(uFragments.connect(user).setMonetaryPolicy(policyAddress)).to 117 | .be.reverted 118 | }) 119 | }) 120 | 121 | describe('UFragments:Rebase:accessControl', async () => { 122 | let user: Signer, userAddress: string 123 | 124 | before('setup UFragments contract', async function () { 125 | await setupContracts() 126 | user = accounts[1] 127 | userAddress = await user.getAddress() 128 | await uFragments.connect(deployer).setMonetaryPolicy(userAddress) 129 | }) 130 | 131 | it('should be callable by monetary policy', async function () { 132 | await expect(uFragments.connect(user).rebase(1, transferAmount)).to.not.be 133 | .reverted 134 | }) 135 | 136 | it('should not be callable by others', async function () { 137 | await expect(uFragments.connect(deployer).rebase(1, transferAmount)).to.be 138 | .reverted 139 | }) 140 | }) 141 | 142 | describe('UFragments:Rebase:Expansion', async () => { 143 | // Rebase +5M (10%), with starting balances A:750 and B:250. 144 | let A: Signer, B: Signer, policy: Signer 145 | const rebaseAmt = INITIAL_SUPPLY.div(10) 146 | 147 | before('setup UFragments contract', async function () { 148 | await setupContracts() 149 | A = accounts[2] 150 | B = accounts[3] 151 | policy = accounts[1] 152 | await uFragments 153 | .connect(deployer) 154 | .setMonetaryPolicy(await policy.getAddress()) 155 | await uFragments 156 | .connect(deployer) 157 | .transfer(await A.getAddress(), toUFrgDenomination('750')) 158 | await uFragments 159 | .connect(deployer) 160 | .transfer(await B.getAddress(), toUFrgDenomination('250')) 161 | 162 | expect(await uFragments.totalSupply()).to.eq(INITIAL_SUPPLY) 163 | expect(await uFragments.balanceOf(await A.getAddress())).to.eq( 164 | toUFrgDenomination('750'), 165 | ) 166 | expect(await uFragments.balanceOf(await B.getAddress())).to.eq( 167 | toUFrgDenomination('250'), 168 | ) 169 | 170 | expect(await uFragments.scaledTotalSupply()).to.eq(TOTAL_GONS) 171 | expect(await uFragments.scaledBalanceOf(await A.getAddress())).to.eq( 172 | '1736881338559742931353564775130318617799049769984608460591863250000000000', 173 | ) 174 | expect(await uFragments.scaledBalanceOf(await B.getAddress())).to.eq( 175 | '578960446186580977117854925043439539266349923328202820197287750000000000', 176 | ) 177 | }) 178 | 179 | it('should emit Rebase', async function () { 180 | await expect(uFragments.connect(policy).rebase(1, rebaseAmt)) 181 | .to.emit(uFragments, 'LogRebase') 182 | .withArgs(1, initialSupply.add(rebaseAmt)) 183 | }) 184 | 185 | it('should increase the totalSupply', async function () { 186 | expect(await uFragments.totalSupply()).to.eq(initialSupply.add(rebaseAmt)) 187 | }) 188 | 189 | it('should NOT CHANGE the scaledTotalSupply', async function () { 190 | expect(await uFragments.scaledTotalSupply()).to.eq(TOTAL_GONS) 191 | }) 192 | 193 | it('should increase individual balances', async function () { 194 | expect(await uFragments.balanceOf(await A.getAddress())).to.eq( 195 | toUFrgDenomination('825'), 196 | ) 197 | expect(await uFragments.balanceOf(await B.getAddress())).to.eq( 198 | toUFrgDenomination('275'), 199 | ) 200 | }) 201 | 202 | it('should NOT CHANGE the individual scaled balances', async function () { 203 | expect(await uFragments.scaledBalanceOf(await A.getAddress())).to.eq( 204 | '1736881338559742931353564775130318617799049769984608460591863250000000000', 205 | ) 206 | expect(await uFragments.scaledBalanceOf(await B.getAddress())).to.eq( 207 | '578960446186580977117854925043439539266349923328202820197287750000000000', 208 | ) 209 | }) 210 | 211 | it('should return the new supply', async function () { 212 | const returnVal = await uFragments 213 | .connect(policy) 214 | .callStatic.rebase(2, rebaseAmt) 215 | await uFragments.connect(policy).rebase(2, rebaseAmt) 216 | expect(await uFragments.totalSupply()).to.eq(returnVal) 217 | }) 218 | }) 219 | 220 | describe('UFragments:Rebase:Expansion', async function () { 221 | let policy: Signer 222 | const MAX_SUPPLY = ethers.BigNumber.from(2).pow(128).sub(1) 223 | 224 | describe('when totalSupply is less than MAX_SUPPLY and expands beyond', function () { 225 | before('setup UFragments contract', async function () { 226 | await setupContracts() 227 | policy = accounts[1] 228 | await uFragments 229 | .connect(deployer) 230 | .setMonetaryPolicy(await policy.getAddress()) 231 | const totalSupply = await uFragments.totalSupply.call() 232 | await uFragments 233 | .connect(policy) 234 | .rebase(1, MAX_SUPPLY.sub(totalSupply).sub(toUFrgDenomination('1'))) 235 | }) 236 | 237 | it('should emit Rebase', async function () { 238 | await expect( 239 | uFragments.connect(policy).rebase(2, toUFrgDenomination('2')), 240 | ) 241 | .to.emit(uFragments, 'LogRebase') 242 | .withArgs(2, MAX_SUPPLY) 243 | }) 244 | 245 | it('should increase the totalSupply to MAX_SUPPLY', async function () { 246 | expect(await uFragments.totalSupply()).to.eq(MAX_SUPPLY) 247 | }) 248 | }) 249 | 250 | describe('when totalSupply is MAX_SUPPLY and expands', function () { 251 | before(async function () { 252 | expect(await uFragments.totalSupply()).to.eq(MAX_SUPPLY) 253 | }) 254 | 255 | it('should emit Rebase', async function () { 256 | await expect( 257 | uFragments.connect(policy).rebase(3, toUFrgDenomination('2')), 258 | ) 259 | .to.emit(uFragments, 'LogRebase') 260 | .withArgs(3, MAX_SUPPLY) 261 | }) 262 | 263 | it('should NOT change the totalSupply', async function () { 264 | expect(await uFragments.totalSupply()).to.eq(MAX_SUPPLY) 265 | }) 266 | }) 267 | }) 268 | 269 | describe('UFragments:Rebase:NoChange', function () { 270 | // Rebase (0%), with starting balances A:750 and B:250. 271 | let A: Signer, B: Signer, policy: Signer 272 | 273 | before('setup UFragments contract', async function () { 274 | await setupContracts() 275 | A = accounts[2] 276 | B = accounts[3] 277 | policy = accounts[1] 278 | await uFragments 279 | .connect(deployer) 280 | .setMonetaryPolicy(await policy.getAddress()) 281 | await uFragments 282 | .connect(deployer) 283 | .transfer(await A.getAddress(), toUFrgDenomination('750')) 284 | await uFragments 285 | .connect(deployer) 286 | .transfer(await B.getAddress(), toUFrgDenomination('250')) 287 | 288 | expect(await uFragments.totalSupply()).to.eq(INITIAL_SUPPLY) 289 | expect(await uFragments.balanceOf(await A.getAddress())).to.eq( 290 | toUFrgDenomination('750'), 291 | ) 292 | expect(await uFragments.balanceOf(await B.getAddress())).to.eq( 293 | toUFrgDenomination('250'), 294 | ) 295 | 296 | expect(await uFragments.scaledTotalSupply()).to.eq(TOTAL_GONS) 297 | expect(await uFragments.scaledBalanceOf(await A.getAddress())).to.eq( 298 | '1736881338559742931353564775130318617799049769984608460591863250000000000', 299 | ) 300 | expect(await uFragments.scaledBalanceOf(await B.getAddress())).to.eq( 301 | '578960446186580977117854925043439539266349923328202820197287750000000000', 302 | ) 303 | }) 304 | 305 | it('should emit Rebase', async function () { 306 | await expect(uFragments.connect(policy).rebase(1, 0)) 307 | .to.emit(uFragments, 'LogRebase') 308 | .withArgs(1, initialSupply) 309 | }) 310 | 311 | it('should NOT CHANGE the totalSupply', async function () { 312 | expect(await uFragments.totalSupply()).to.eq(initialSupply) 313 | }) 314 | 315 | it('should NOT CHANGE the scaledTotalSupply', async function () { 316 | expect(await uFragments.scaledTotalSupply()).to.eq(TOTAL_GONS) 317 | }) 318 | 319 | it('should NOT CHANGE individual balances', async function () { 320 | expect(await uFragments.balanceOf(await A.getAddress())).to.eq( 321 | toUFrgDenomination('750'), 322 | ) 323 | expect(await uFragments.balanceOf(await B.getAddress())).to.eq( 324 | toUFrgDenomination('250'), 325 | ) 326 | }) 327 | 328 | it('should NOT CHANGE the individual scaled balances', async function () { 329 | expect(await uFragments.scaledBalanceOf(await A.getAddress())).to.eq( 330 | '1736881338559742931353564775130318617799049769984608460591863250000000000', 331 | ) 332 | expect(await uFragments.scaledBalanceOf(await B.getAddress())).to.eq( 333 | '578960446186580977117854925043439539266349923328202820197287750000000000', 334 | ) 335 | }) 336 | }) 337 | 338 | describe('UFragments:Rebase:Contraction', function () { 339 | // Rebase -5M (-10%), with starting balances A:750 and B:250. 340 | let A: Signer, B: Signer, policy: Signer 341 | const rebaseAmt = INITIAL_SUPPLY.div(10) 342 | 343 | before('setup UFragments contract', async function () { 344 | await setupContracts() 345 | A = accounts[2] 346 | B = accounts[3] 347 | policy = accounts[1] 348 | await uFragments 349 | .connect(deployer) 350 | .setMonetaryPolicy(await policy.getAddress()) 351 | await uFragments 352 | .connect(deployer) 353 | .transfer(await A.getAddress(), toUFrgDenomination('750')) 354 | await uFragments 355 | .connect(deployer) 356 | .transfer(await B.getAddress(), toUFrgDenomination('250')) 357 | 358 | expect(await uFragments.totalSupply()).to.eq(INITIAL_SUPPLY) 359 | expect(await uFragments.balanceOf(await A.getAddress())).to.eq( 360 | toUFrgDenomination('750'), 361 | ) 362 | expect(await uFragments.balanceOf(await B.getAddress())).to.eq( 363 | toUFrgDenomination('250'), 364 | ) 365 | 366 | expect(await uFragments.scaledTotalSupply()).to.eq(TOTAL_GONS) 367 | expect(await uFragments.scaledBalanceOf(await A.getAddress())).to.eq( 368 | '1736881338559742931353564775130318617799049769984608460591863250000000000', 369 | ) 370 | expect(await uFragments.scaledBalanceOf(await B.getAddress())).to.eq( 371 | '578960446186580977117854925043439539266349923328202820197287750000000000', 372 | ) 373 | }) 374 | 375 | it('should emit Rebase', async function () { 376 | await expect(uFragments.connect(policy).rebase(1, -rebaseAmt)) 377 | .to.emit(uFragments, 'LogRebase') 378 | .withArgs(1, initialSupply.sub(rebaseAmt)) 379 | }) 380 | 381 | it('should decrease the totalSupply', async function () { 382 | expect(await uFragments.totalSupply()).to.eq(initialSupply.sub(rebaseAmt)) 383 | }) 384 | 385 | it('should NOT. CHANGE the scaledTotalSupply', async function () { 386 | expect(await uFragments.scaledTotalSupply()).to.eq(TOTAL_GONS) 387 | }) 388 | 389 | it('should decrease individual balances', async function () { 390 | expect(await uFragments.balanceOf(await A.getAddress())).to.eq( 391 | toUFrgDenomination('675'), 392 | ) 393 | expect(await uFragments.balanceOf(await B.getAddress())).to.eq( 394 | toUFrgDenomination('225'), 395 | ) 396 | }) 397 | 398 | it('should NOT CHANGE the individual scaled balances', async function () { 399 | expect(await uFragments.scaledBalanceOf(await A.getAddress())).to.eq( 400 | '1736881338559742931353564775130318617799049769984608460591863250000000000', 401 | ) 402 | expect(await uFragments.scaledBalanceOf(await B.getAddress())).to.eq( 403 | '578960446186580977117854925043439539266349923328202820197287750000000000', 404 | ) 405 | }) 406 | }) 407 | 408 | describe('UFragments:Transfer', function () { 409 | let A: Signer, B: Signer, C: Signer 410 | 411 | before('setup UFragments contract', async () => { 412 | await setupContracts() 413 | A = accounts[2] 414 | B = accounts[3] 415 | C = accounts[4] 416 | }) 417 | 418 | describe('deployer transfers 12 to A', function () { 419 | it('should have correct balances', async function () { 420 | const deployerBefore = await uFragments.balanceOf( 421 | await deployer.getAddress(), 422 | ) 423 | await uFragments 424 | .connect(deployer) 425 | .transfer(await A.getAddress(), toUFrgDenomination('12')) 426 | expect(await uFragments.balanceOf(await deployer.getAddress())).to.eq( 427 | deployerBefore.sub(toUFrgDenomination('12')), 428 | ) 429 | expect(await uFragments.balanceOf(await A.getAddress())).to.eq( 430 | toUFrgDenomination('12'), 431 | ) 432 | }) 433 | }) 434 | 435 | describe('deployer transfers 15 to B', async function () { 436 | it('should have balances [973,15]', async function () { 437 | const deployerBefore = await uFragments.balanceOf( 438 | await deployer.getAddress(), 439 | ) 440 | await uFragments 441 | .connect(deployer) 442 | .transfer(await B.getAddress(), toUFrgDenomination('15')) 443 | expect(await uFragments.balanceOf(await deployer.getAddress())).to.eq( 444 | deployerBefore.sub(toUFrgDenomination('15')), 445 | ) 446 | expect(await uFragments.balanceOf(await B.getAddress())).to.eq( 447 | toUFrgDenomination('15'), 448 | ) 449 | }) 450 | }) 451 | 452 | describe('deployer transfers the rest to C', async function () { 453 | it('should have balances [0,973]', async function () { 454 | const deployerBefore = await uFragments.balanceOf( 455 | await deployer.getAddress(), 456 | ) 457 | await uFragments 458 | .connect(deployer) 459 | .transfer(await C.getAddress(), deployerBefore) 460 | expect(await uFragments.balanceOf(await deployer.getAddress())).to.eq(0) 461 | expect(await uFragments.balanceOf(await C.getAddress())).to.eq( 462 | deployerBefore, 463 | ) 464 | }) 465 | }) 466 | 467 | describe('when the recipient address is the contract address', function () { 468 | it('reverts on transfer', async function () { 469 | await expect( 470 | uFragments.connect(A).transfer(uFragments.address, unitTokenAmount), 471 | ).to.be.reverted 472 | }) 473 | 474 | it('reverts on transferFrom', async function () { 475 | await expect( 476 | uFragments 477 | .connect(A) 478 | .transferFrom( 479 | await A.getAddress(), 480 | uFragments.address, 481 | unitTokenAmount, 482 | ), 483 | ).to.be.reverted 484 | }) 485 | }) 486 | 487 | describe('when the recipient is the zero address', function () { 488 | it('emits an approval event', async function () { 489 | await expect( 490 | uFragments 491 | .connect(A) 492 | .approve(ethers.constants.AddressZero, transferAmount), 493 | ) 494 | .to.emit(uFragments, 'Approval') 495 | .withArgs( 496 | await A.getAddress(), 497 | ethers.constants.AddressZero, 498 | transferAmount, 499 | ) 500 | }) 501 | 502 | it('transferFrom should fail', async function () { 503 | await expect( 504 | uFragments 505 | .connect(C) 506 | .transferFrom( 507 | await A.getAddress(), 508 | ethers.constants.AddressZero, 509 | unitTokenAmount, 510 | ), 511 | ).to.be.reverted 512 | }) 513 | }) 514 | }) 515 | -------------------------------------------------------------------------------- /test/unit/UFragments_erc20_permit.ts: -------------------------------------------------------------------------------- 1 | import { network, ethers, upgrades } from 'hardhat' 2 | import { Contract, Signer, Wallet, BigNumber } from 'ethers' 3 | import { expect } from 'chai' 4 | 5 | import { 6 | EIP712_DOMAIN_TYPEHASH, 7 | EIP2612_PERMIT_TYPEHASH, 8 | getDomainSeparator, 9 | signEIP712Permission, 10 | } from '../utils/signatures' 11 | 12 | let accounts: Signer[], 13 | deployer: Signer, 14 | deployerAddress: string, 15 | owner: Wallet, 16 | ownerAddress: string, 17 | spender: Wallet, 18 | spenderAddress: string, 19 | uFragments: Contract, 20 | initialSupply: BigNumber 21 | 22 | async function setupContracts() { 23 | // prepare signers 24 | accounts = await ethers.getSigners() 25 | deployer = accounts[0] 26 | deployerAddress = await deployer.getAddress() 27 | 28 | owner = Wallet.createRandom() 29 | ownerAddress = await owner.getAddress() 30 | 31 | spender = Wallet.createRandom() 32 | spenderAddress = await spender.getAddress() 33 | 34 | // deploy upgradable token 35 | const factory = await ethers.getContractFactory('UFragments') 36 | uFragments = await upgrades.deployProxy(factory, [deployerAddress], { 37 | initializer: 'initialize(address)', 38 | }) 39 | // fetch initial supply 40 | initialSupply = await uFragments.totalSupply() 41 | } 42 | 43 | // https://eips.ethereum.org/EIPS/eip-2612 44 | // Test cases as in: 45 | // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/drafts/ERC20Permit.test.js 46 | describe('UFragments:Initialization', () => { 47 | before('setup UFragments contract', setupContracts) 48 | 49 | it('should set the EIP2612 parameters', async function () { 50 | expect(await uFragments.EIP712_REVISION()).to.eq('1') 51 | expect(await uFragments.EIP712_DOMAIN()).to.eq(EIP712_DOMAIN_TYPEHASH) 52 | expect(await uFragments.PERMIT_TYPEHASH()).to.eq(EIP2612_PERMIT_TYPEHASH) 53 | // with hard-coded parameters 54 | expect(await uFragments.DOMAIN_SEPARATOR()).to.eq( 55 | getDomainSeparator( 56 | await uFragments.EIP712_REVISION(), 57 | await uFragments.name(), 58 | uFragments.address, 59 | network.config.chainId || 1, 60 | ), 61 | ) 62 | }) 63 | 64 | it('initial nonce is 0', async function () { 65 | expect(await uFragments.nonces(deployerAddress)).to.eq('0') 66 | expect(await uFragments.nonces(ownerAddress)).to.eq('0') 67 | expect(await uFragments.nonces(spenderAddress)).to.eq('0') 68 | }) 69 | }) 70 | 71 | // Using the cases specified by: 72 | // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/drafts/ERC20Permit.test.js 73 | describe('UFragments:EIP-2612 Permit', () => { 74 | const MAX_DEADLINE = BigNumber.from(2).pow(256).sub(1) 75 | 76 | beforeEach('setup UFragments contract', setupContracts) 77 | 78 | describe('permit', function () { 79 | const signPermission = async ( 80 | signer: Wallet, 81 | owner: string, 82 | spender: string, 83 | value: number, 84 | nonce: number, 85 | deadline: BigNumber, 86 | ) => { 87 | return signEIP712Permission( 88 | await uFragments.EIP712_REVISION(), 89 | await uFragments.name(), 90 | uFragments.address, 91 | network.config.chainId || 1, 92 | signer, 93 | owner, 94 | spender, 95 | value, 96 | nonce, 97 | deadline, 98 | ) 99 | } 100 | 101 | it('accepts owner signature', async function () { 102 | const { v, r, s } = await signPermission( 103 | owner, 104 | ownerAddress, 105 | spenderAddress, 106 | 123, 107 | 0, 108 | MAX_DEADLINE, 109 | ) 110 | await expect( 111 | uFragments 112 | .connect(deployer) 113 | .permit(ownerAddress, spenderAddress, 123, MAX_DEADLINE, v, r, s), 114 | ) 115 | .to.emit(uFragments, 'Approval') 116 | .withArgs(ownerAddress, spenderAddress, '123') 117 | expect(await uFragments.nonces(ownerAddress)).to.eq('1') 118 | expect(await uFragments.allowance(ownerAddress, spenderAddress)).to.eq( 119 | '123', 120 | ) 121 | }) 122 | 123 | it('rejects reused signature', async function () { 124 | const { v, r, s } = await signPermission( 125 | owner, 126 | ownerAddress, 127 | spenderAddress, 128 | 123, 129 | 0, 130 | MAX_DEADLINE, 131 | ) 132 | await uFragments 133 | .connect(deployer) 134 | .permit(ownerAddress, spenderAddress, 123, MAX_DEADLINE, v, r, s) 135 | await expect( 136 | uFragments 137 | .connect(deployer) 138 | .permit(ownerAddress, spenderAddress, 123, MAX_DEADLINE, v, r, s), 139 | ).to.be.reverted 140 | }) 141 | 142 | it('rejects other signature', async function () { 143 | const { v, r, s } = await signPermission( 144 | spender, 145 | ownerAddress, 146 | spenderAddress, 147 | 123, 148 | 0, 149 | MAX_DEADLINE, 150 | ) 151 | await expect( 152 | uFragments 153 | .connect(deployer) 154 | .permit(ownerAddress, spenderAddress, 123, MAX_DEADLINE, v, r, s), 155 | ).to.be.reverted 156 | }) 157 | 158 | it('rejects expired permit', async function () { 159 | const currentTs = (await ethers.provider.getBlock('latest')).timestamp 160 | const olderTs = currentTs - 3600 * 24 * 7 161 | const deadline = BigNumber.from(olderTs) 162 | const { v, r, s } = await signPermission( 163 | owner, 164 | ownerAddress, 165 | spenderAddress, 166 | 123, 167 | 0, 168 | deadline, 169 | ) 170 | await expect( 171 | uFragments 172 | .connect(deployer) 173 | .permit(ownerAddress, spenderAddress, 123, deadline, v, r, s), 174 | ).to.be.reverted 175 | }) 176 | }) 177 | }) 178 | -------------------------------------------------------------------------------- /test/unit/UInt256Lib.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | import { Contract } from 'ethers' 3 | import { expect } from 'chai' 4 | 5 | describe('UInt256Lib', () => { 6 | const MAX_INT256 = ethers.BigNumber.from(2).pow(255).sub(1) 7 | 8 | let UInt256Lib: Contract 9 | 10 | beforeEach(async function () { 11 | // deploy contract 12 | const factory = await ethers.getContractFactory('UInt256LibMock') 13 | UInt256Lib = await factory.deploy() 14 | await UInt256Lib.deployed() 15 | }) 16 | 17 | describe('toInt256Safe', function () { 18 | describe('when then number is more than MAX_INT256', () => { 19 | it('should fail', async function () { 20 | await expect(UInt256Lib.toInt256Safe(MAX_INT256.add(1))).to.be.reverted 21 | }) 22 | }) 23 | 24 | describe('when then number is MAX_INT256', () => { 25 | it('converts int to uint256 safely', async function () { 26 | await expect(UInt256Lib.toInt256Safe(MAX_INT256)) 27 | .to.emit(UInt256Lib, 'ReturnValueInt256') 28 | .withArgs(MAX_INT256) 29 | }) 30 | }) 31 | 32 | describe('when then number is less than MAX_INT256', () => { 33 | it('converts int to uint256 safely', async function () { 34 | await expect(UInt256Lib.toInt256Safe(MAX_INT256.sub(1))) 35 | .to.emit(UInt256Lib, 'ReturnValueInt256') 36 | .withArgs(MAX_INT256.sub(1)) 37 | }) 38 | }) 39 | 40 | describe('when then number is 0', () => { 41 | it('converts int to uint256 safely', async function () { 42 | await expect(UInt256Lib.toInt256Safe(0)) 43 | .to.emit(UInt256Lib, 'ReturnValueInt256') 44 | .withArgs(0) 45 | }) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/unit/uFragments_elastic_behavior.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades, waffle } from 'hardhat' 2 | import { Contract, Signer, BigNumber } from 'ethers' 3 | import { TransactionResponse } from '@ethersproject/providers' 4 | import { expect } from 'chai' 5 | 6 | const DECIMALS = 9 7 | const INITIAL_SUPPLY = ethers.utils.parseUnits('50', 6 + DECIMALS) 8 | const MAX_UINT256 = ethers.BigNumber.from(2).pow(256).sub(1) 9 | const MAX_INT256 = ethers.BigNumber.from(2).pow(255).sub(1) 10 | const TOTAL_GONS = MAX_UINT256.sub(MAX_UINT256.mod(INITIAL_SUPPLY)) 11 | 12 | const toUFrgDenomination = (ample: string): BigNumber => 13 | ethers.utils.parseUnits(ample, DECIMALS) 14 | 15 | const unitTokenAmount = toUFrgDenomination('1') 16 | 17 | let token: Contract, owner: Signer, anotherAccount: Signer, recipient: Signer 18 | 19 | async function upgradeableToken() { 20 | const [owner, recipient, anotherAccount] = await ethers.getSigners() 21 | const factory = await ethers.getContractFactory('UFragments') 22 | const token = await upgrades.deployProxy( 23 | factory.connect(owner), 24 | [await owner.getAddress()], 25 | { 26 | initializer: 'initialize(address)', 27 | }, 28 | ) 29 | return { token, owner, recipient, anotherAccount } 30 | } 31 | 32 | describe('UFragments:Elastic', () => { 33 | beforeEach('setup UFragments contract', async function () { 34 | ;({ token, owner, recipient, anotherAccount } = await waffle.loadFixture( 35 | upgradeableToken, 36 | )) 37 | }) 38 | 39 | describe('scaledTotalSupply', function () { 40 | it('returns the scaled total amount of tokens', async function () { 41 | expect(await token.scaledTotalSupply()).to.eq(TOTAL_GONS) 42 | }) 43 | }) 44 | 45 | describe('scaledBalanceOf', function () { 46 | describe('when the requested account has no tokens', function () { 47 | it('returns zero', async function () { 48 | expect( 49 | await token.scaledBalanceOf(await anotherAccount.getAddress()), 50 | ).to.eq(0) 51 | }) 52 | }) 53 | 54 | describe('when the requested account has some tokens', function () { 55 | it('returns the total amount of tokens', async function () { 56 | expect(await token.scaledBalanceOf(await owner.getAddress())).to.eq( 57 | TOTAL_GONS, 58 | ) 59 | }) 60 | }) 61 | }) 62 | }) 63 | 64 | describe('UFragments:Elastic:transferAll', () => { 65 | beforeEach('setup UFragments contract', async function () { 66 | ;({ token, owner, recipient, anotherAccount } = await waffle.loadFixture( 67 | upgradeableToken, 68 | )) 69 | }) 70 | 71 | describe('when the recipient is the zero address', function () { 72 | it('should revert', async function () { 73 | await expect( 74 | token.connect(owner).transferAll(ethers.constants.AddressZero), 75 | ).to.be.reverted 76 | }) 77 | }) 78 | 79 | describe('when the recipient is the contract address', function () { 80 | it('should revert', async function () { 81 | await expect(token.connect(owner).transferAll(token.address)).to.be 82 | .reverted 83 | }) 84 | }) 85 | 86 | describe('when the sender has zero balance', function () { 87 | it('should not revert', async function () { 88 | await expect( 89 | token.connect(anotherAccount).transferAll(await owner.getAddress()), 90 | ).not.to.be.reverted 91 | }) 92 | }) 93 | 94 | describe('when the sender has balance', function () { 95 | it('should emit a transfer event', async function () { 96 | await expect( 97 | token.connect(owner).transferAll(await recipient.getAddress()), 98 | ) 99 | .to.emit(token, 'Transfer') 100 | .withArgs( 101 | await owner.getAddress(), 102 | await recipient.getAddress(), 103 | INITIAL_SUPPLY, 104 | ) 105 | }) 106 | 107 | it("should transfer all of the sender's balance", async function () { 108 | const senderBalance = await token.balanceOf(await owner.getAddress()) 109 | const recipientBalance = await token.balanceOf( 110 | await recipient.getAddress(), 111 | ) 112 | await token.connect(owner).transferAll(await recipient.getAddress()) 113 | const senderBalance_ = await token.balanceOf(await owner.getAddress()) 114 | const recipientBalance_ = await token.balanceOf( 115 | await recipient.getAddress(), 116 | ) 117 | expect(senderBalance_).to.eq('0') 118 | expect(recipientBalance_.sub(recipientBalance)).to.eq(senderBalance) 119 | }) 120 | }) 121 | }) 122 | 123 | describe('UFragments:Elastic:transferAllFrom', () => { 124 | beforeEach('setup UFragments contract', async function () { 125 | ;({ token, owner, recipient, anotherAccount } = await waffle.loadFixture( 126 | upgradeableToken, 127 | )) 128 | }) 129 | 130 | describe('when the recipient is the zero address', function () { 131 | it('should revert', async function () { 132 | const senderBalance = await token.balanceOf(await owner.getAddress()) 133 | await token 134 | .connect(owner) 135 | .approve(await anotherAccount.getAddress(), senderBalance) 136 | await expect( 137 | token 138 | .connect(anotherAccount) 139 | .transferAllFrom( 140 | await owner.getAddress(), 141 | ethers.constants.AddressZero, 142 | ), 143 | ).to.be.reverted 144 | }) 145 | }) 146 | 147 | describe('when the recipient is the contract address', function () { 148 | it('should revert', async function () { 149 | const senderBalance = await token.balanceOf(await owner.getAddress()) 150 | await token 151 | .connect(owner) 152 | .approve(await anotherAccount.getAddress(), senderBalance) 153 | await expect( 154 | token 155 | .connect(anotherAccount) 156 | .transferAllFrom(await owner.getAddress(), token.address), 157 | ).to.be.reverted 158 | }) 159 | }) 160 | 161 | describe('when the sender has zero balance', function () { 162 | it('should not revert', async function () { 163 | const senderBalance = await token.balanceOf( 164 | await anotherAccount.getAddress(), 165 | ) 166 | await token 167 | .connect(anotherAccount) 168 | .approve(await anotherAccount.getAddress(), senderBalance) 169 | 170 | await expect( 171 | token 172 | .connect(recipient) 173 | .transferAllFrom( 174 | await anotherAccount.getAddress(), 175 | await recipient.getAddress(), 176 | ), 177 | ).not.to.be.reverted 178 | }) 179 | }) 180 | 181 | describe('when the spender does NOT have enough approved balance', function () { 182 | it('reverts', async function () { 183 | await token 184 | .connect(owner) 185 | .approve(await anotherAccount.getAddress(), unitTokenAmount) 186 | await expect( 187 | token 188 | .connect(anotherAccount) 189 | .transferAllFrom( 190 | await owner.getAddress(), 191 | await recipient.getAddress(), 192 | ), 193 | ).to.be.reverted 194 | }) 195 | }) 196 | 197 | describe('when the spender has enough approved balance', function () { 198 | it('emits a transfer event', async function () { 199 | const senderBalance = await token.balanceOf(await owner.getAddress()) 200 | await token 201 | .connect(owner) 202 | .approve(await anotherAccount.getAddress(), senderBalance) 203 | 204 | await expect( 205 | token 206 | .connect(anotherAccount) 207 | .transferAllFrom( 208 | await owner.getAddress(), 209 | await recipient.getAddress(), 210 | ), 211 | ) 212 | .to.emit(token, 'Transfer') 213 | .withArgs( 214 | await owner.getAddress(), 215 | await recipient.getAddress(), 216 | senderBalance, 217 | ) 218 | }) 219 | 220 | it('transfers the requested amount', async function () { 221 | const senderBalance = await token.balanceOf(await owner.getAddress()) 222 | const recipientBalance = await token.balanceOf( 223 | await recipient.getAddress(), 224 | ) 225 | 226 | await token 227 | .connect(owner) 228 | .approve(await anotherAccount.getAddress(), senderBalance) 229 | 230 | await token 231 | .connect(anotherAccount) 232 | .transferAllFrom(await owner.getAddress(), await recipient.getAddress()) 233 | 234 | const senderBalance_ = await token.balanceOf(await owner.getAddress()) 235 | const recipientBalance_ = await token.balanceOf( 236 | await recipient.getAddress(), 237 | ) 238 | expect(senderBalance_).to.eq('0') 239 | expect(recipientBalance_.sub(recipientBalance)).to.eq(senderBalance) 240 | }) 241 | 242 | it('decreases the spender allowance', async function () { 243 | const senderBalance = await token.balanceOf(await owner.getAddress()) 244 | await token 245 | .connect(owner) 246 | .approve(await anotherAccount.getAddress(), senderBalance.add('99')) 247 | await token 248 | .connect(anotherAccount) 249 | .transferAllFrom(await owner.getAddress(), await recipient.getAddress()) 250 | expect( 251 | await token.allowance( 252 | await owner.getAddress(), 253 | await anotherAccount.getAddress(), 254 | ), 255 | ).to.eq('99') 256 | }) 257 | }) 258 | }) 259 | -------------------------------------------------------------------------------- /test/utils/signatures.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/albertocuestacanada/ERC20Permit/blob/master/utils/signatures.ts 2 | import { 3 | keccak256, 4 | defaultAbiCoder, 5 | toUtf8Bytes, 6 | solidityPack, 7 | splitSignature, 8 | } from 'ethers/lib/utils' 9 | import { ecsign } from 'ethereumjs-util' 10 | import { BigNumberish, Wallet } from 'ethers' 11 | 12 | export const EIP712_DOMAIN_TYPEHASH = keccak256( 13 | toUtf8Bytes( 14 | 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)', 15 | ), 16 | ) 17 | 18 | export const EIP712_DOMAIN_TYPE = [ 19 | { name: 'name', type: 'string' }, 20 | { name: 'version', type: 'string' }, 21 | { name: 'chainId', type: 'uint256' }, 22 | { name: 'verifyingContract', type: 'address' }, 23 | ] 24 | 25 | export const EIP2612_PERMIT_TYPEHASH = keccak256( 26 | toUtf8Bytes( 27 | 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)', 28 | ), 29 | ) 30 | 31 | export const EIP2612_PERMIT_TYPE = [ 32 | { name: 'owner', type: 'address' }, 33 | { name: 'spender', type: 'address' }, 34 | { name: 'value', type: 'uint256' }, 35 | { name: 'nonce', type: 'uint256' }, 36 | { name: 'deadline', type: 'uint256' }, 37 | ] 38 | 39 | // Gets the EIP712 domain separator 40 | export function getDomainSeparator( 41 | version: string, 42 | name: string, 43 | contractAddress: string, 44 | chainId: number, 45 | ) { 46 | return keccak256( 47 | defaultAbiCoder.encode( 48 | ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'], 49 | [ 50 | EIP712_DOMAIN_TYPEHASH, 51 | keccak256(toUtf8Bytes(name)), 52 | keccak256(toUtf8Bytes(version)), 53 | chainId, 54 | contractAddress, 55 | ], 56 | ), 57 | ) 58 | } 59 | 60 | // Returns the EIP712 hash which should be signed by the user 61 | // in order to make a call to `permit` 62 | export function getPermitDigest( 63 | version: string, 64 | name: string, 65 | address: string, 66 | chainId: number, 67 | owner: string, 68 | spender: string, 69 | value: number, 70 | nonce: number, 71 | deadline: BigNumberish, 72 | ) { 73 | const DOMAIN_SEPARATOR = getDomainSeparator(version, name, address, chainId) 74 | const permitHash = keccak256( 75 | defaultAbiCoder.encode( 76 | ['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'], 77 | [EIP2612_PERMIT_TYPEHASH, owner, spender, value, nonce, deadline], 78 | ), 79 | ) 80 | const hash = keccak256( 81 | solidityPack( 82 | ['bytes1', 'bytes1', 'bytes32', 'bytes32'], 83 | ['0x19', '0x01', DOMAIN_SEPARATOR, permitHash], 84 | ), 85 | ) 86 | return hash 87 | } 88 | 89 | export const signEIP712Permission = async ( 90 | version: string, 91 | name: string, 92 | verifyingContract: string, 93 | chainId: number, 94 | signer: Wallet, 95 | owner: string, 96 | spender: string, 97 | value: number, 98 | nonce: number, 99 | deadline: BigNumberish, 100 | ) => { 101 | const domain = { 102 | name, 103 | version, 104 | chainId, 105 | verifyingContract, 106 | } 107 | 108 | const types = { Permit: EIP2612_PERMIT_TYPE } 109 | 110 | const data = { 111 | owner, 112 | spender, 113 | value, 114 | nonce, 115 | deadline, 116 | } 117 | 118 | const signature = await signer._signTypedData(domain, types, data) 119 | 120 | return splitSignature(signature) 121 | } 122 | -------------------------------------------------------------------------------- /test/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | import { BigNumberish } from 'ethers' 3 | import { BigNumber as BN } from 'bignumber.js' 4 | 5 | export const imul = (a: BigNumberish, b: BigNumberish, c: BigNumberish) => { 6 | return ethers.BigNumber.from( 7 | new BN(a.toString()).times(b.toString()).idiv(c.toString()).toString(10), 8 | ) 9 | } 10 | 11 | export const increaseTime = async (seconds: BigNumberish) => { 12 | const now = (await ethers.provider.getBlock('latest')).timestamp 13 | await ethers.provider.send('evm_mine', [ 14 | ethers.BigNumber.from(seconds).add(now).toNumber(), 15 | ]) 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "commonjs", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "resolveJsonModule": true, 9 | "downlevelIteration": true, 10 | "outDir": "dist" 11 | }, 12 | "include": ["./scripts", "./test"], 13 | "files": [ 14 | "hardhat.config.ts", 15 | "./node_modules/@nomiclabs/hardhat-ethers/src/type-extensions.ts", 16 | "./node_modules/@nomiclabs/hardhat-waffle/src/type-extensions.ts", 17 | "./node_modules/@openzeppelin/hardhat-upgrades/src/type-extensions.ts" 18 | ] 19 | } --------------------------------------------------------------------------------