diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5898837 --- /dev/null +++ b/.env.example @@ -0,0 +1,26 @@ +## Add URI and BIP39 mnemonic, and etherscan API key for every network that you plan to use +## These are used in `hardhat.config.ts` (see `nodeUrl` and `accounts` calls in network definitions) +#ETH_NODE_URI_FORK="" +#MNEMONIC_FORK="" +#ETHERSCAN_API_KEY="" + +## To do mainnet fork in Polygon +#ETH_NODE_URI_FORKPOLYGON="" + +## To do a local Ethereum mainnet fork +#ETH_NODE_URI_MAINNETFORKREMOTE="" + +#ETH_NODE_URI_GOERLI="" +#MNEMONIC_GOERLI="" +#GOERLI_ETHERSCAN_API_KEY="" + +#ETH_NODE_URI_MAINNET="" +#MNEMONIC_MAINNET="" +#MAINNET_ETHERSCAN_API_KEY="" + +#ETH_NODE_URI_POLYGON="" +#MNEMONIC_POLYGON="" +#POLYGON_ETHERSCAN_API_KEY="" + +#ETH_NODE_URI_FANTOM="" + diff --git a/.github/actions/setup-repo/action.yml b/.github/actions/setup-repo/action.yml new file mode 100644 index 0000000..1932a67 --- /dev/null +++ b/.github/actions/setup-repo/action.yml @@ -0,0 +1,35 @@ +name: Setup repo +description: Runs all steps to setup the repo (install node_modules, build, etc...) +inputs: + registry-token: + description: 'PAT to access registries' +runs: + using: 'composite' + steps: + - name: Get yarn cache directory path + id: yarn-cache-dir-path + shell: bash + run: | + echo "::set-output name=dir::$(yarn cache dir)" + echo "::set-output name=version::$(yarn -v)" + + - uses: actions/setup-node@v3 + with: + node-version: '20' + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: | + **/node_modules + ${{ steps.yarn-cache-dir-path.outputs.dir }} + + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install dependencies + shell: bash + run: echo "//npm.pkg.github.com/:_authToken=$GH_REGISTRY_ACCESS_TOKEN" >> .npmrc && yarn install --frozen-lockfile --verbose && rm -f .npmrc + env: + GH_REGISTRY_ACCESS_TOKEN: ${{ inputs.registry-token }} diff --git a/.github/assets/logo.svg b/.github/assets/logo.svg new file mode 100644 index 0000000..ccd539a --- /dev/null +++ b/.github/assets/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/.github/workflows/ci-deep.yml b/.github/workflows/ci-deep.yml new file mode 100644 index 0000000..c1a1435 --- /dev/null +++ b/.github/workflows/ci-deep.yml @@ -0,0 +1,173 @@ +name: "CI Deep" + +env: + FOUNDRY_PROFILE: "ci" + +on: + schedule: + - cron: "0 3 * * 0" # at 3:00am UTC every Sunday + workflow_dispatch: + inputs: + fuzzRuns: + default: "10000" + description: "Unit: number of fuzz runs." + required: false + invariantRuns: + default: "300" + description: "Unit: number of invariant runs." + required: false + invariantDepth: + default: "50" + description: "Unit: invariant depth." + required: false + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + registry-token: ${{ secrets.GH_REGISTRY_ACCESS_TOKEN }} + + - name: Run solhint + run: yarn lint:check + + - name: "Add lint summary" + run: | + echo "## Lint result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: "recursive" + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + registry-token: ${{ secrets.GH_REGISTRY_ACCESS_TOKEN }} + + - name: Compile foundry + run: yarn compile --sizes + + - name: "Cache the build so that it can be re-used by the other jobs" + uses: "actions/cache/save@v3" + with: + key: "build-${{ github.sha }}" + path: | + cache-forge + out + node_modules + + - name: "Add build summary" + run: | + echo "## Build result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + test-unit: + needs: ["build", "lint"] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: "recursive" + + - uses: actions/cache/restore@v3 + with: + fail-on-cache-miss: true + path: | + cache-forge + out + node_modules + key: "build-${{ github.sha }}" + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Foundry tests + run: yarn test:unit + env: + ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} + ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} + ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + + # test-invariant: + # needs: ["build", "lint"] + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # with: + # submodules: "recursive" +# + # - uses: actions/cache/restore@v3 + # with: + # fail-on-cache-miss: true + # path: | + # cache-forge + # out + # node_modules + # key: "build-${{ github.sha }}" +# + # - name: Install Foundry + # uses: foundry-rs/foundry-toolchain@v1 + # with: + # version: nightly +# + # - name: Run Foundry tests + # run: yarn test:invariant + # env: + # FOUNDRY_INVARIANT_RUNS: ${{ github.event.inputs.invariantRuns || '300' }} + # FOUNDRY_INVARIANT_DEPTH: ${{ github.event.inputs.invariantDepth || '50' }} + # ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} + # ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} + # ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + # ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} +# + # test-fuzz: + # needs: ["build", "lint"] + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # with: + # submodules: "recursive" +# + # - uses: actions/cache/restore@v3 + # with: + # fail-on-cache-miss: true + # path: | + # cache-forge + # out + # node_modules + # key: "build-${{ github.sha }}" +# + # - name: Install Foundry + # uses: foundry-rs/foundry-toolchain@v1 + # with: + # version: nightly +# + # - name: Run Foundry tests + # run: yarn test:fuzz + # env: + # FOUNDRY_FUZZ_RUNS: ${{ github.event.inputs.fuzzRuns || '10000' }} + # ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} + # ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} + # ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + # ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cb4b2ee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,242 @@ +name: "CI" + +env: + FOUNDRY_PROFILE: "ci" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - "main" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + registry-token: ${{ secrets.GH_REGISTRY_ACCESS_TOKEN }} + + - name: Run solhint + run: yarn lint:check + + - name: "Add lint summary" + run: | + echo "## Lint result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: "recursive" + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Setup repo + uses: ./.github/actions/setup-repo + with: + registry-token: ${{ secrets.GH_REGISTRY_ACCESS_TOKEN }} + + - name: Compile foundry + run: yarn compile --sizes + + - name: "Cache the build so that it can be re-used by the other jobs" + uses: "actions/cache/save@v3" + with: + key: "build-${{ github.sha }}" + path: | + cache-forge + out + node_modules + + - name: "Add build summary" + run: | + echo "## Build result" >> $GITHUB_STEP_SUMMARY + echo "✅ Passed" >> $GITHUB_STEP_SUMMARY + + test-unit: + needs: ["build", "lint"] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: "recursive" + + - uses: actions/cache/restore@v3 + with: + fail-on-cache-miss: true + path: | + cache-forge + out + node_modules + key: "build-${{ github.sha }}" + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Foundry tests + run: yarn test:unit + env: + ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} + ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} + ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + + # test-invariant: + # needs: ["build", "lint"] + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # with: + # submodules: "recursive" +# + # - uses: actions/cache/restore@v3 + # with: + # fail-on-cache-miss: true + # path: | + # cache-forge + # out + # node_modules + # key: "build-${{ github.sha }}" +# + # - name: Install Foundry + # uses: foundry-rs/foundry-toolchain@v1 + # with: + # version: nightly +# + # - name: Run Foundry tests + # run: yarn test:invariant + # env: + # FOUNDRY_INVARIANT_RUNS: "8" + # FOUNDRY_INVARIANT_DEPTH: "256" + # ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} + # ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} + # ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + # ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} +# + # test-fuzz: + # needs: ["build", "lint"] + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # with: + # submodules: "recursive" +# + # - uses: actions/cache/restore@v3 + # with: + # fail-on-cache-miss: true + # path: | + # cache-forge + # out + # node_modules + # key: "build-${{ github.sha }}" +# + # - name: Install Foundry + # uses: foundry-rs/foundry-toolchain@v1 + # with: + # version: nightly +# + # - name: Run Foundry tests + # run: yarn test:fuzz + # env: + # FOUNDRY_FUZZ_RUNS: "5000" + # ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} + # ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} + # ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + # ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + + coverage: + needs: ["build", "lint"] + runs-on: "ubuntu-latest" + steps: + - uses: actions/checkout@v3 + with: + submodules: "recursive" + + - uses: actions/cache/restore@v3 + with: + fail-on-cache-miss: true + path: | + cache-forge + out + node_modules + key: "build-${{ github.sha }}" + + - name: "Install Foundry" + uses: "foundry-rs/foundry-toolchain@v1" + + - name: "Install lcov" + run: "sudo apt-get install lcov" + + - name: "Generate the coverage report using the unit and the integration tests" + run: "yarn ci:coverage" + env: + ETH_NODE_URI_POLYGON: ${{ secrets.ETH_NODE_URI_POLYGON }} + ETH_NODE_URI_ARBITRUM: ${{ secrets.ETH_NODE_URI_ARBITRUM }} + ETH_NODE_URI_OPTIMISM: ${{ secrets.ETH_NODE_URI_OPTIMISM }} + ETH_NODE_URI_MAINNET: ${{ secrets.ETH_NODE_URI_MAINNET }} + + - name: "Upload coverage report to Codecov" + uses: "codecov/codecov-action@v3" + with: + files: "./lcov.info" + token: ${{ secrets.CODECOV_TOKEN }} + + - name: "Add coverage summary" + run: | + echo "## Coverage result" >> $GITHUB_STEP_SUMMARY + echo "✅ Uploaded to Codecov" >> $GITHUB_STEP_SUMMARY + + slither-analyze: + needs: ["build", "lint"] + runs-on: "ubuntu-latest" + permissions: + actions: "read" + contents: "read" + security-events: "write" + steps: + - uses: actions/checkout@v3 + with: + submodules: "recursive" + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Compile foundry + run: forge clean && forge build --build-info --force + + - name: "Run Slither analysis" + uses: "crytic/slither-action@v0.3.2" + id: "slither" + with: + ignore-compile: true + fail-on: "none" + sarif: "results.sarif" + + - name: "Upload SARIF file to GitHub code scanning" + uses: "github/codeql-action/upload-sarif@v2" + with: + sarif_file: ${{ steps.slither.outputs.sarif }} + + - name: "Add Slither summary" + run: | + echo "## Slither result" >> $GITHUB_STEP_SUMMARY + echo "✅ Uploaded to GitHub code scanning" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 9282e82..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: test - -on: workflow_dispatch - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Run Forge build - run: | - forge --version - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test diff --git a/.gitignore b/.gitignore index 85198aa..ebe338c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,53 @@ -# Compiler files -cache/ -out/ +## Defaults +__pycache__ +.idea +.DS_Store +.deps +.docs +.env +node_modules +tenderly.yaml +settings.json +venv -# Ignores development broadcast logs -!/broadcast -/broadcast/*/31337/ -/broadcast/**/dry-run/ +# Build output +/cache +/cache-hh +build +export +**/artifacts +.openzeppelin +lcov.info +docgen/docs +docgen/SUMMARY.md +solidity-flattenedContracts +./crytic-export +typechain +slither-audit.txt +slither -# Docs -docs/ +# Test output +coverage +coverage.json -# Dotenv file -.env +# Running output +gas-report.txt +gasReporterOutput.json +addresses.json +blockchain_db +ganache* +yarn-error.log + +# deployments +deployments/localhost +deployments/mainnetForkRemote + +# bin +bin + +# temporary delete +typechain/cacheIndex.ts + +# foundry +/out +/cache-forge diff --git a/.gitmodules b/.gitmodules index 888d42d..2a230a9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,18 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/utils"] + path = lib/utils + url = https://github.com/AngleProtocol/utils +[submodule "lib/chainlink"] + path = lib/chainlink + url = https://github.com/smartcontractkit/chainlink +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/LayerZero"] + path = lib/LayerZero + url = https://github.com/LayerZero-Labs/LayerZero diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..af66bba --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@angleprotocol:registry=https://npm.pkg.github.com diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..de01738 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +lib +artifacts +node_modules +cache-forge +cache-hh +export +out +typechain \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..295b1da --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +{ + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 120, + "singleQuote": false, + "bracketSpacing": true + } + } + ] +} diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 0000000..939f263 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,28 @@ +{ + "extends": "solhint:recommended", + "plugins": ["prettier"], + "rules": { + "max-line-length": ["warn", 120], + "avoid-call-value": "warn", + "avoid-low-level-calls": "off", + "avoid-tx-origin": "warn", + "const-name-snakecase": "warn", + "contract-name-camelcase": "warn", + "imports-on-top": "warn", + "prettier/prettier": "error", + "ordering": "off", + "max-states-count": "off", + "mark-callable-contracts": "off", + "no-empty-blocks": "off", + "no-global-import": "off", + "not-rely-on-time": "off", + "compiler-version": "off", + "private-vars-leading-underscore": "warn", + "reentrancy": "warn", + "no-inline-assembly": "off", + "no-complex-fallback": "off", + "reason-string": "off", + "func-visibility": ["warn", { "ignoreConstructors": true }], + "explicit-types": ["error","explicit"] + } +} diff --git a/.solhintignore b/.solhintignore new file mode 100644 index 0000000..6a51c68 --- /dev/null +++ b/.solhintignore @@ -0,0 +1,12 @@ +# Doesn't need to lint dev files +lib +scripts +test + +# Doesn't need to lint build files +node_modules +cache-forge +out + +# Doesn't need to lint utils +external \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..216fc85 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,20 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Generate Header", + "type": "shell", + "command": "headers ${input:header}", + "presentation": { + "reveal": "never" + } + } + ], + "inputs": [ + { + "id": "header", + "description": "Header", + "type": "promptString" + } + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ef8ff11 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +contact@angle.money. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..de7db21 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Submit a change or a new feature + +First of all thank you for your interest in this repository! + +This is only the beginning of the Angle protocol and codebase, and anyone is welcome to improve it. + +To submit some code, please work in a fork, reach out to explain what you've done and open a Pull Request from your fork. + +Feel free to reach out in the [#developers channel](https://discord.gg/HcRB8QMeKU) of our Discord Server if you need a hand! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index 9265b45..014c19c 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,165 @@ -## Foundry +# Angle Angle Tokens -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +[![CI](https://github.com/AngleProtocol/boilerplate/actions/workflows/ci.yml/badge.svg)](https://github.com/AngleProtocol/boilerplate/actions) -Foundry consists of: +This repository contains all the contracts of the Angle Tokens with associated contracts (CoreBorrow, FlashAngle ...) -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +## Starting -## Documentation +### Install packages -https://book.getfoundry.sh/ +You can install all dependencies by running -## Usage +```bash +yarn +forge i +``` + +### Create `.env` file + +In order to interact with non local networks, you must create an `.env` that has: + +- `PRIVATE_KEY` +- `MNEMONIC` +- network key (eg. `ALCHEMY_NETWORK_KEY`) +- `ETHERSCAN_API_KEY` + +For additional keys, you can check the `.env.example` file. + +Warning: always keep your confidential information safe. + +## Headers + +To automatically create headers, follow: + +## Hardhat Command line completion + +Follow these instructions to have hardhat command line arguments completion: + +## Foundry Installation + +```bash +curl -L https://foundry.paradigm.xyz | bash + +source /root/.zshrc +# or, if you're under bash: source /root/.bashrc + +foundryup +``` + +To install the standard library: + +```bash +forge install foundry-rs/forge-std +``` + +To update libraries: + +```bash +forge update +``` -### Build +### Foundry on Docker 🐳 -```shell -$ forge build +**If you don’t want to install Rust and Foundry on your computer, you can use Docker** +Image is available here [ghcr.io/foundry-rs/foundry](http://ghcr.io/foundry-rs/foundry). + +```bash +docker pull ghcr.io/foundry-rs/foundry +docker tag ghcr.io/foundry-rs/foundry:latest foundry:latest ``` -### Test +To run the container: -```shell -$ forge test +```bash +docker run -it --rm -v $(pwd):/app -w /app foundry sh ``` -### Format +Then you are inside the container and can run Foundry’s commands. + +### Tests + +You can run tests as follows: -```shell -$ forge fmt +```bash +forge test -vvvv --watch +forge test -vvvv --match-path contracts/forge-tests/KeeperMulticall.t.sol +forge test -vvvv --match-test "testAbc*" +forge test -vvvv --fork-url https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf ``` -### Gas Snapshots +You can also list tests: -```shell -$ forge snapshot +```bash +forge test --list +forge test --list --json --match-test "testXXX*" ``` -### Anvil +### Deploying -```shell -$ anvil +There is an example script in the `scripts/foundry` folder. Then you can run: + +```bash +yarn foundry:deploy --rpc-url ``` -### Deploy +Example: -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +```bash +yarn foundry:deploy scripts/foundry/DeployMockAgEUR.s.sol --rpc-url goerli ``` -### Cast +### Coverage + +We recommend the use of this [vscode extension](ryanluker.vscode-coverage-gutters). -```shell -$ cast +```bash +yarn hardhat:coverage +yarn foundry:coverage ``` -### Help +### Simulate + +You can simulate your transaction live or in fork mode. For both option you need to +complete the `scripts/foundry/Simulate.s.sol` with your values: address sending the tx, +address caled and the data to give to this address call. -```shell -$ forge --help -$ anvil --help -$ cast --help +For live simulation + +```bash +yarn foundry:simulate ``` + +For fork simulation + +```bash +yarn foundry:fork +yarn foundry:simulate:fork +``` + +For fork simulation at a given block + +```bash +yarn foundry:fork:block ${XXXX} +yarn foundry:simulate:fork +``` + +### Gas report + +```bash +yarn foundry:gas +``` + +## Slither + +```bash +pip3 install slither-analyzer +pip3 install solc-select +solc-select install 0.8.11 +solc-select use 0.8.11 +slither . +``` + +## Media + +Don't hesitate to reach out on [Twitter](https://twitter.com/AngleProtocol) 🐦 diff --git a/contracts/agToken/AgEUR.sol b/contracts/agToken/AgEUR.sol new file mode 100644 index 0000000..3338bd7 --- /dev/null +++ b/contracts/agToken/AgEUR.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "../interfaces/IAgToken.sol"; +import "../interfaces/coreModule/IStableMaster.sol"; +import "../interfaces/ITreasury.sol"; +import "oz-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; + +/// @title AgEUR +/// @author Angle Labs, Inc. +/// @notice Base contract for agEUR, Angle's Euro stablecoin +/// @dev This contract is an upgraded version of the agEUR contract that was first deployed on Ethereum mainnet +contract AgEUR is IAgToken, ERC20PermitUpgradeable { + // ================================= REFERENCES ================================ + + /// @notice Reference to the `StableMaster` contract associated to agEUR + address public stableMaster; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + // ============================== ADDED PARAMETERS ============================= + + /// @inheritdoc IAgToken + mapping(address => bool) public isMinter; + /// @notice Reference to the treasury contract which can grant minting rights + address public treasury; + /// @notice Boolean used to check whether the contract had been reinitialized after an upgrade + bool public treasuryInitialized; + + // =================================== EVENTS ================================== + + event TreasuryUpdated(address indexed _treasury); + event MinterToggled(address indexed minter); + + // =================================== ERRORS ================================== + + error BurnAmountExceedsAllowance(); + error InvalidSender(); + error InvalidTreasury(); + error NotGovernor(); + error NotMinter(); + error NotTreasury(); + error TreasuryAlreadyInitialized(); + + // ================================= MODIFIERS ================================= + + /// @notice Checks to see if it is the `Treasury` calling this contract + modifier onlyTreasury() { + if (msg.sender != treasury) revert NotTreasury(); + _; + } + + /// @notice Checks whether the sender has the minting right + modifier onlyMinter() { + if (!isMinter[msg.sender]) revert NotMinter(); + _; + } + + // ============================= EXTERNAL FUNCTION ============================= + + /// @notice Allows anyone to burn stablecoins + /// @param amount Amount of stablecoins to burn + /// @dev This function can typically be called if there is a settlement mechanism to burn stablecoins + function burnStablecoin(uint256 amount) external { + _burn(msg.sender, amount); + } + + // ========================= MINTER ROLE ONLY FUNCTIONS ======================== + + /// @inheritdoc IAgToken + function burnSelf(uint256 amount, address burner) external onlyMinter { + _burn(burner, amount); + } + + /// @inheritdoc IAgToken + function burnFrom(uint256 amount, address burner, address sender) external onlyMinter { + if (burner != sender) { + uint256 currentAllowance = allowance(burner, sender); + if (currentAllowance < amount) revert BurnAmountExceedsAllowance(); + _approve(burner, sender, currentAllowance - amount); + } + _burn(burner, amount); + } + + /// @inheritdoc IAgToken + function mint(address account, uint256 amount) external onlyMinter { + _mint(account, amount); + } + + // ========================== TREASURY ONLY FUNCTIONS ========================== + + /// @inheritdoc IAgToken + function addMinter(address minter) external onlyTreasury { + isMinter[minter] = true; + emit MinterToggled(minter); + } + + /// @inheritdoc IAgToken + function removeMinter(address minter) external { + if (msg.sender != minter && msg.sender != address(treasury)) revert InvalidSender(); + isMinter[minter] = false; + emit MinterToggled(minter); + } + + /// @inheritdoc IAgToken + function setTreasury(address _treasury) external onlyTreasury { + treasury = _treasury; + emit TreasuryUpdated(_treasury); + } +} diff --git a/contracts/agToken/AgToken.sol b/contracts/agToken/AgToken.sol new file mode 100644 index 0000000..75db571 --- /dev/null +++ b/contracts/agToken/AgToken.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +/* + * █ + ***** ▓▓▓ + * ▓▓▓▓▓▓▓ + * ///. ▓▓▓▓▓▓▓▓▓▓▓▓▓ + ***** //////// ▓▓▓▓▓▓▓ + * ///////////// ▓▓▓ + ▓▓ ////////////////// █ ▓▓ + ▓▓ ▓▓ /////////////////////// ▓▓ ▓▓ + ▓▓ ▓▓ //////////////////////////// ▓▓ ▓▓ + ▓▓ ▓▓ /////////▓▓▓///////▓▓▓///////// ▓▓ ▓▓ + ▓▓ ,////////////////////////////////////// ▓▓ ▓▓ + ▓▓ ////////////////////////////////////////// ▓▓ + ▓▓ //////////////////////▓▓▓▓///////////////////// + ,//////////////////////////////////////////////////// + .////////////////////////////////////////////////////////// + .//////////////////////////██.,//////////////////////////█ + .//////////////////////████..,./////////////////////██ + ...////////////////███████.....,.////////////////███ + ,.,////////////████████ ........,///////////████ + .,.,//////█████████ ,.......///////████ + ,..//████████ ........./████ + ..,██████ .....,███ + .██ ,.,█ + + + + ▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓ ▓▓▓▓ + ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓▓▓▓ + ▓▓▓ ▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ +*/ + +import "../interfaces/IAgToken.sol"; +import "../interfaces/ITreasury.sol"; +import "oz-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; + +/// @title AgToken +/// @author Angle Labs, Inc. +/// @notice Base contract for Angle agTokens on Ethereum and on other chains +/// @dev By default, agTokens are ERC-20 tokens with 18 decimals +contract AgToken is IAgToken, ERC20PermitUpgradeable { + // =========================== PARAMETERS / VARIABLES ========================== + + /// @inheritdoc IAgToken + mapping(address => bool) public isMinter; + /// @notice Reference to the treasury contract which can grant minting rights + address public treasury; + + // =================================== EVENTS ================================== + + event TreasuryUpdated(address indexed _treasury); + event MinterToggled(address indexed minter); + + // =================================== ERRORS ================================== + + error BurnAmountExceedsAllowance(); + error InvalidSender(); + error InvalidTreasury(); + error NotMinter(); + error NotTreasury(); + + // ================================ CONSTRUCTOR ================================ + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + /// @notice Initializes the `AgToken` contract + function initialize(string memory name_, string memory symbol_, address _treasury) external { + _initialize(name_, symbol_, _treasury); + } + + /// @notice Initializes the contract + function _initialize(string memory name_, string memory symbol_, address _treasury) internal virtual initializer { + if (address(ITreasury(_treasury).stablecoin()) != address(this)) revert InvalidTreasury(); + __ERC20Permit_init(name_); + __ERC20_init(name_, symbol_); + treasury = _treasury; + emit TreasuryUpdated(_treasury); + } + + // ================================= MODIFIERS ================================= + + /// @notice Checks to see if it is the `Treasury` calling this contract + modifier onlyTreasury() { + if (msg.sender != treasury) revert NotTreasury(); + _; + } + + /// @notice Checks whether the sender has the minting right + modifier onlyMinter() { + if (!isMinter[msg.sender]) revert NotMinter(); + _; + } + + // ============================= EXTERNAL FUNCTION ============================= + + /// @notice Allows anyone to burn stablecoins + /// @param amount Amount of stablecoins to burn + /// @dev This function can typically be called if there is a settlement mechanism to burn stablecoins + function burnStablecoin(uint256 amount) external { + _burn(msg.sender, amount); + } + + // ========================= MINTER ROLE ONLY FUNCTIONS ======================== + + /// @inheritdoc IAgToken + function burnSelf(uint256 amount, address burner) external onlyMinter { + _burn(burner, amount); + } + + /// @inheritdoc IAgToken + function burnFrom(uint256 amount, address burner, address sender) external onlyMinter { + if (burner != sender) { + uint256 currentAllowance = allowance(burner, sender); + if (currentAllowance < amount) revert BurnAmountExceedsAllowance(); + _approve(burner, sender, currentAllowance - amount); + } + _burn(burner, amount); + } + + /// @inheritdoc IAgToken + function mint(address account, uint256 amount) external onlyMinter { + _mint(account, amount); + } + + // ========================== GOVERNANCE ONLY FUNCTIONS ========================== + + /// @inheritdoc IAgToken + function addMinter(address minter) external onlyTreasury { + isMinter[minter] = true; + emit MinterToggled(minter); + } + + /// @inheritdoc IAgToken + function removeMinter(address minter) external { + if (msg.sender != address(treasury) && msg.sender != minter) revert InvalidSender(); + isMinter[minter] = false; + emit MinterToggled(minter); + } + + /// @inheritdoc IAgToken + function setTreasury(address _treasury) external virtual onlyTreasury { + treasury = _treasury; + emit TreasuryUpdated(_treasury); + } +} diff --git a/contracts/agToken/AgTokenSideChainMultiBridge.sol b/contracts/agToken/AgTokenSideChainMultiBridge.sol new file mode 100644 index 0000000..956d1b9 --- /dev/null +++ b/contracts/agToken/AgTokenSideChainMultiBridge.sol @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "./AgToken.sol"; +import "oz/token/ERC20/IERC20.sol"; +import "oz/token/ERC20/utils/SafeERC20.sol"; + +/// @title AgTokenSideChainMultiBridge +/// @author Angle Labs, Inc. +/// @notice Contract for Angle agTokens on other chains than Ethereum mainnet +/// @dev This contract supports bridge tokens having a minting right on the stablecoin +/// (also referred to as the canonicalor the native token) +contract AgTokenSideChainMultiBridge is AgToken { + using SafeERC20 for IERC20; + + /// @notice Base used for fee computation + uint256 public constant BASE_PARAMS = 1e9; + + // =============================== BRIDGING DATA =============================== + + /// @notice Struct with some data about a specific bridge token + struct BridgeDetails { + // Limit on the balance of bridge token held by the contract: it is designed + // to reduce the exposure of the system to hacks + uint256 limit; + // Limit on the hourly volume of token minted through this bridge + // Technically the limit over a rolling hour is hourlyLimit x2 as hourly limit + // is enforced only between x:00 and x+1:00 + uint256 hourlyLimit; + // Fee taken for swapping in and out the token + uint64 fee; + // Whether the associated token is allowed or not + bool allowed; + // Whether swapping in and out from the associated token is paused or not + bool paused; + } + + /// @notice Maps a bridge token to data + mapping(address => BridgeDetails) public bridges; + /// @notice List of all bridge tokens + address[] public bridgeTokensList; + /// @notice Maps a bridge token to the associated hourly volume + mapping(address => mapping(uint256 => uint256)) public usage; + /// @notice Maps an address to whether it is exempt of fees for when it comes to swapping in and out + mapping(address => uint256) public isFeeExempt; + /// @notice Limit to the amount of tokens that can be sent from that chain to another chain + uint256 public chainTotalHourlyLimit; + /// @notice Usage per hour on that chain. Maps an hourly timestamp to the total volume swapped out on the chain + mapping(uint256 => uint256) public chainTotalUsage; + + uint256[44] private __gapMultiBridge; + + // =================================== EVENTS ================================== + + event BridgeTokenAdded(address indexed bridgeToken, uint256 limit, uint256 hourlyLimit, uint64 fee, bool paused); + event BridgeTokenToggled(address indexed bridgeToken, bool toggleStatus); + event BridgeTokenRemoved(address indexed bridgeToken); + event BridgeTokenFeeUpdated(address indexed bridgeToken, uint64 fee); + event BridgeTokenLimitUpdated(address indexed bridgeToken, uint256 limit); + event BridgeTokenHourlyLimitUpdated(address indexed bridgeToken, uint256 hourlyLimit); + event HourlyLimitUpdated(uint256 hourlyLimit); + event Recovered(address indexed token, address indexed to, uint256 amount); + event FeeToggled(address indexed theAddress, uint256 toggleStatus); + + // =================================== ERRORS ================================== + + error AssetStillControlledInReserves(); + error HourlyLimitExceeded(); + error InvalidToken(); + error NotGovernor(); + error NotGovernorOrGuardian(); + error TooBigAmount(); + error TooHighParameterValue(); + error ZeroAddress(); + + // ================================= MODIFIERS ================================= + + /// @notice Checks whether the `msg.sender` has the governor role or not + modifier onlyGovernor() { + if (!ITreasury(treasury).isGovernor(msg.sender)) revert NotGovernor(); + _; + } + + /// @notice Checks whether the `msg.sender` has the governor role or the guardian role + modifier onlyGovernorOrGuardian() { + if (!ITreasury(treasury).isGovernorOrGuardian(msg.sender)) revert NotGovernorOrGuardian(); + _; + } + + // ===================== EXTERNAL PERMISSIONLESS FUNCTIONS ===================== + + /// @notice Returns the list of all supported bridge tokens + /// @dev Helpful for UIs + function allBridgeTokens() external view returns (address[] memory) { + return bridgeTokensList; + } + + /// @notice Returns the current volume for a bridge, for the current hour + /// @param bridgeToken Bridge used to mint + /// @dev Helpful for UIs + function currentUsage(address bridgeToken) external view returns (uint256) { + return usage[bridgeToken][block.timestamp / 3600]; + } + + /// @notice Returns the current total volume on the chain for the current hour + /// @dev Helpful for UIs + function currentTotalUsage() external view returns (uint256) { + return chainTotalUsage[block.timestamp / 3600]; + } + + /// @notice Mints the canonical token from a supported bridge token + /// @param bridgeToken Bridge token to use to mint + /// @param amount Amount of bridge tokens to send + /// @param to Address to which the stablecoin should be sent + /// @return Amount of the canonical stablecoin actually minted + /// @dev Some fees may be taken by the protocol depending on the token used and on the address calling + function swapIn(address bridgeToken, uint256 amount, address to) external returns (uint256) { + BridgeDetails memory bridgeDetails = bridges[bridgeToken]; + if (!bridgeDetails.allowed || bridgeDetails.paused) revert InvalidToken(); + uint256 balance = IERC20(bridgeToken).balanceOf(address(this)); + if (balance + amount > bridgeDetails.limit) { + // In case someone maliciously sends tokens to this contract + // Or the limit changes + if (bridgeDetails.limit > balance) amount = bridgeDetails.limit - balance; + else { + amount = 0; + } + } + + // Checking requirement on the hourly volume + uint256 hour = block.timestamp / 3600; + uint256 hourlyUsage = usage[bridgeToken][hour]; + if (hourlyUsage + amount > bridgeDetails.hourlyLimit) { + // Edge case when the hourly limit changes + amount = bridgeDetails.hourlyLimit > hourlyUsage ? bridgeDetails.hourlyLimit - hourlyUsage : 0; + } + usage[bridgeToken][hour] = hourlyUsage + amount; + + IERC20(bridgeToken).safeTransferFrom(msg.sender, address(this), amount); + uint256 canonicalOut = amount; + // Computing fees + if (isFeeExempt[msg.sender] == 0) { + canonicalOut -= (canonicalOut * bridgeDetails.fee) / BASE_PARAMS; + } + _mint(to, canonicalOut); + return canonicalOut; + } + + /// @notice Burns the canonical token in exchange for a bridge token + /// @param bridgeToken Bridge token required + /// @param amount Amount of canonical tokens to burn + /// @param to Address to which the bridge token should be sent + /// @return Amount of bridge tokens actually sent back + /// @dev Some fees may be taken by the protocol depending on the token used and on the address calling + function swapOut(address bridgeToken, uint256 amount, address to) external returns (uint256) { + BridgeDetails memory bridgeDetails = bridges[bridgeToken]; + if (!bridgeDetails.allowed || bridgeDetails.paused) revert InvalidToken(); + + uint256 hour = block.timestamp / 3600; + uint256 hourlyUsage = chainTotalUsage[hour] + amount; + // If the amount being swapped out exceeds the limit, we revert + // We don't want to change the amount being swapped out. + // The user can decide to send another tx with the correct amount to reach the limit + if (hourlyUsage > chainTotalHourlyLimit) revert HourlyLimitExceeded(); + chainTotalUsage[hour] = hourlyUsage; + + _burn(msg.sender, amount); + uint256 bridgeOut = amount; + if (isFeeExempt[msg.sender] == 0) { + bridgeOut -= (bridgeOut * bridgeDetails.fee) / BASE_PARAMS; + } + IERC20(bridgeToken).safeTransfer(to, bridgeOut); + return bridgeOut; + } + + // ============================ GOVERNANCE FUNCTIONS =========================== + + /// @notice Adds support for a bridge token + /// @param bridgeToken Bridge token to add: it should be a version of the stablecoin from another bridge + /// @param limit Limit on the balance of bridge token this contract could hold + /// @param hourlyLimit Limit on the hourly volume for this bridge + /// @param paused Whether swapping for this token should be paused or not + /// @param fee Fee taken upon swapping for or against this token + function addBridgeToken( + address bridgeToken, + uint256 limit, + uint256 hourlyLimit, + uint64 fee, + bool paused + ) external onlyGovernor { + if (bridges[bridgeToken].allowed || bridgeToken == address(0)) revert InvalidToken(); + if (fee > BASE_PARAMS) revert TooHighParameterValue(); + BridgeDetails memory _bridge; + _bridge.limit = limit; + _bridge.hourlyLimit = hourlyLimit; + _bridge.paused = paused; + _bridge.fee = fee; + _bridge.allowed = true; + bridges[bridgeToken] = _bridge; + bridgeTokensList.push(bridgeToken); + emit BridgeTokenAdded(bridgeToken, limit, hourlyLimit, fee, paused); + } + + /// @notice Removes support for a token + /// @param bridgeToken Address of the bridge token to remove support for + function removeBridgeToken(address bridgeToken) external onlyGovernor { + if (IERC20(bridgeToken).balanceOf(address(this)) != 0) revert AssetStillControlledInReserves(); + delete bridges[bridgeToken]; + // Deletion from `bridgeTokensList` loop + uint256 bridgeTokensListLength = bridgeTokensList.length; + for (uint256 i; i < bridgeTokensListLength - 1; ++i) { + if (bridgeTokensList[i] == bridgeToken) { + // Replace the `bridgeToken` to remove with the last of the list + bridgeTokensList[i] = bridgeTokensList[bridgeTokensListLength - 1]; + break; + } + } + // Remove last element in array + bridgeTokensList.pop(); + emit BridgeTokenRemoved(bridgeToken); + } + + /// @notice Recovers any ERC20 token + /// @dev Can be used to withdraw bridge tokens for them to be de-bridged on mainnet + function recoverERC20(address tokenAddress, address to, uint256 amountToRecover) external onlyGovernor { + IERC20(tokenAddress).safeTransfer(to, amountToRecover); + emit Recovered(tokenAddress, to, amountToRecover); + } + + /// @notice Updates the `limit` amount for `bridgeToken` + function setLimit(address bridgeToken, uint256 limit) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bridges[bridgeToken].limit = limit; + emit BridgeTokenLimitUpdated(bridgeToken, limit); + } + + /// @notice Updates the `hourlyLimit` amount for `bridgeToken` + function setHourlyLimit(address bridgeToken, uint256 hourlyLimit) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bridges[bridgeToken].hourlyLimit = hourlyLimit; + emit BridgeTokenHourlyLimitUpdated(bridgeToken, hourlyLimit); + } + + /// @notice Updates the `chainTotalHourlyLimit` amount + function setChainTotalHourlyLimit(uint256 hourlyLimit) external onlyGovernorOrGuardian { + chainTotalHourlyLimit = hourlyLimit; + emit HourlyLimitUpdated(hourlyLimit); + } + + /// @notice Updates the `fee` value for `bridgeToken` + function setSwapFee(address bridgeToken, uint64 fee) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + if (fee > BASE_PARAMS) revert TooHighParameterValue(); + bridges[bridgeToken].fee = fee; + emit BridgeTokenFeeUpdated(bridgeToken, fee); + } + + /// @notice Pauses or unpauses swapping in and out for a token + function toggleBridge(address bridgeToken) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bool pausedStatus = bridges[bridgeToken].paused; + bridges[bridgeToken].paused = !pausedStatus; + emit BridgeTokenToggled(bridgeToken, !pausedStatus); + } + + /// @notice Toggles fees for the address `theAddress` + function toggleFeesForAddress(address theAddress) external onlyGovernorOrGuardian { + uint256 feeExemptStatus = 1 - isFeeExempt[theAddress]; + isFeeExempt[theAddress] = feeExemptStatus; + emit FeeToggled(theAddress, feeExemptStatus); + } +} diff --git a/contracts/agToken/TokenSideChainMultiBridge.sol b/contracts/agToken/TokenSideChainMultiBridge.sol new file mode 100644 index 0000000..a3270ca --- /dev/null +++ b/contracts/agToken/TokenSideChainMultiBridge.sol @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "oz/token/ERC20/IERC20.sol"; +import "oz/token/ERC20/utils/SafeERC20.sol"; +import "oz-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; + +import "../interfaces/ICoreBorrow.sol"; + +/// @title TokenSideChainMultiBridge +/// @author Angle Labs, Inc. +/// @notice Contract for an ERC20 token that exists natively on a chain on other chains than this native chain +/// @dev This contract supports bridge tokens having a minting right on the ERC20 token (also referred to as the canonical +/// of the chain) +/// @dev This implementation assumes that the token bridged has 18 decimals +contract TokenSideChainMultiBridge is ERC20PermitUpgradeable { + using SafeERC20 for IERC20; + + /// @notice Struct with some data about a specific bridge token + struct BridgeDetails { + // Limit on the balance of bridge token held by the contract: it is designed + // to reduce the exposure of the system to hacks + uint256 limit; + // Limit on the hourly volume of token minted through this bridge + // Technically the limit over a rolling hour is hourlyLimit x2 as hourly limit + // is enforced only between x:00 and x+1:00 + uint256 hourlyLimit; + // Fee taken for swapping in and out the token + uint64 fee; + // Whether the associated token is allowed or not + bool allowed; + // Whether swapping in and out from the associated token is paused or not + bool paused; + } + + /// @notice Base used for fee computation + uint256 public constant BASE_PARAMS = 10 ** 9; + + // ================================= PARAMETER ================================= + + /// @notice Reference to the core contract which handles access control + ICoreBorrow public core; + + // =============================== BRIDGING DATA =============================== + + /// @notice Maps a bridge token to data + mapping(address => BridgeDetails) public bridges; + /// @notice List of all bridge tokens + address[] public bridgeTokensList; + /// @notice Maps a bridge token to the associated hourly volume + mapping(address => mapping(uint256 => uint256)) public usage; + /// @notice Maps an address to whether it is exempt of fees for when it comes to swapping in and out + mapping(address => uint256) public isFeeExempt; + /// @notice Limit to the amount of tokens that can be sent from that chain to another chain + uint256 public chainTotalHourlyLimit; + /// @notice Usage per hour on that chain. Maps an hourly timestamp to the total volume swapped out on the chain + mapping(uint256 => uint256) public chainTotalUsage; + + // =================================== ERRORS ================================== + + error AssetStillControlledInReserves(); + error HourlyLimitExceeded(); + error InvalidToken(); + error NotGovernor(); + error NotGovernorOrGuardian(); + error TooHighParameterValue(); + error ZeroAddress(); + + // =================================== EVENTS ================================== + + event BridgeTokenAdded(address indexed bridgeToken, uint256 limit, uint256 hourlyLimit, uint64 fee, bool paused); + event BridgeTokenToggled(address indexed bridgeToken, bool toggleStatus); + event BridgeTokenRemoved(address indexed bridgeToken); + event BridgeTokenFeeUpdated(address indexed bridgeToken, uint64 fee); + event BridgeTokenLimitUpdated(address indexed bridgeToken, uint256 limit); + event BridgeTokenHourlyLimitUpdated(address indexed bridgeToken, uint256 hourlyLimit); + event HourlyLimitUpdated(uint256 hourlyLimit); + event Recovered(address indexed token, address indexed to, uint256 amount); + event FeeToggled(address indexed theAddress, uint256 toggleStatus); + + // ================================= MODIFIERS ================================= + + /// @notice Checks whether the `msg.sender` has the governor role or not + modifier onlyGovernor() { + if (!core.isGovernor(msg.sender)) revert NotGovernor(); + _; + } + + /// @notice Checks whether the `msg.sender` has the governor role or the guardian role + modifier onlyGovernorOrGuardian() { + if (!core.isGovernorOrGuardian(msg.sender)) revert NotGovernorOrGuardian(); + _; + } + + // ================================ CONSTRUCTOR ================================ + + /// @notice Initializes the contract + /// @param name_ Name of the token + /// @param symbol_ Symbol of the token + /// @param _core Reference to the `CoreBorrow` contract + /// @dev This implementation allows to add directly at deployment a bridge token + function initialize( + string memory name_, + string memory symbol_, + ICoreBorrow _core, + address bridgeToken, + uint256 limit, + uint256 hourlyLimit, + uint64 fee, + bool paused, + uint256 _chainTotalHourlyLimit + ) external initializer { + __ERC20Permit_init(name_); + __ERC20_init(name_, symbol_); + if (address(_core) == address(0)) revert ZeroAddress(); + core = _core; + _addBridgeToken(bridgeToken, limit, hourlyLimit, fee, paused); + _setChainTotalHourlyLimit(_chainTotalHourlyLimit); + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + // ===================== EXTERNAL PERMISSIONLESS FUNCTIONS ===================== + + /// @notice Returns the list of all supported bridge tokens + /// @dev Helpful for UIs + function allBridgeTokens() external view returns (address[] memory) { + return bridgeTokensList; + } + + /// @notice Returns the current volume for a bridge, for the current hour + /// @param bridgeToken Bridge used to mint + /// @dev Helpful for UIs + function currentUsage(address bridgeToken) external view returns (uint256) { + return usage[bridgeToken][block.timestamp / 3600]; + } + + /// @notice Returns the current total volume on the chain for the current hour + /// @dev Helpful for UIs + function currentTotalUsage() external view returns (uint256) { + return chainTotalUsage[block.timestamp / 3600]; + } + + /// @notice Mints the canonical token from a supported bridge token + /// @param bridgeToken Bridge token to use to mint + /// @param amount Amount of bridge tokens to send + /// @param to Address to which the token should be sent + /// @return Amount of canonical token actually minted + /// @dev Some fees may be taken by the protocol depending on the token used and on the address calling + function swapIn(address bridgeToken, uint256 amount, address to) external returns (uint256) { + BridgeDetails memory bridgeDetails = bridges[bridgeToken]; + if (!bridgeDetails.allowed || bridgeDetails.paused) revert InvalidToken(); + uint256 balance = IERC20(bridgeToken).balanceOf(address(this)); + if (balance + amount > bridgeDetails.limit) { + // In case someone maliciously sends tokens to this contract + // Or the limit changes + if (bridgeDetails.limit > balance) amount = bridgeDetails.limit - balance; + else { + amount = 0; + } + } + + // Checking requirement on the hourly volume + uint256 hour = block.timestamp / 3600; + uint256 hourlyUsage = usage[bridgeToken][hour]; + if (hourlyUsage + amount > bridgeDetails.hourlyLimit) { + // Edge case when the hourly limit changes + amount = bridgeDetails.hourlyLimit > hourlyUsage ? bridgeDetails.hourlyLimit - hourlyUsage : 0; + } + usage[bridgeToken][hour] = hourlyUsage + amount; + + IERC20(bridgeToken).safeTransferFrom(msg.sender, address(this), amount); + uint256 canonicalOut = amount; + // Computing fees + if (isFeeExempt[msg.sender] == 0) { + canonicalOut -= (canonicalOut * bridgeDetails.fee) / BASE_PARAMS; + } + _mint(to, canonicalOut); + return canonicalOut; + } + + /// @notice Burns the canonical token in exchange for a bridge token + /// @param bridgeToken Bridge token required + /// @param amount Amount of canonical tokens to burn + /// @param to Address to which the bridge token should be sent + /// @return Amount of bridge tokens actually sent back + /// @dev Some fees may be taken by the protocol depending on the token used and on the address calling + function swapOut(address bridgeToken, uint256 amount, address to) external returns (uint256) { + BridgeDetails memory bridgeDetails = bridges[bridgeToken]; + if (!bridgeDetails.allowed || bridgeDetails.paused) revert InvalidToken(); + + uint256 hour = block.timestamp / 3600; + uint256 hourlyUsage = chainTotalUsage[hour] + amount; + // If the amount being swapped out exceeds the limit, we revert + // We don't want to change the amount being swapped out. + // The user can decide to send another tx with the correct amount to reach the limit + if (hourlyUsage > chainTotalHourlyLimit) revert HourlyLimitExceeded(); + chainTotalUsage[hour] = hourlyUsage; + + _burn(msg.sender, amount); + uint256 bridgeOut = amount; + if (isFeeExempt[msg.sender] == 0) { + bridgeOut -= (bridgeOut * bridgeDetails.fee) / BASE_PARAMS; + } + IERC20(bridgeToken).safeTransfer(to, bridgeOut); + return bridgeOut; + } + + // ============================ GOVERNANCE FUNCTIONS =========================== + + /// @notice Sets a new `core` contract + /// @dev One sanity check that can be performed here is to verify whether at least the governor + /// calling the contract is still a governor in the new core + function setCore(ICoreBorrow _core) external onlyGovernor { + if (!_core.isGovernor(msg.sender)) revert NotGovernor(); + core = _core; + } + + /// @notice Adds support for a bridge token + /// @param bridgeToken Bridge token to add: it should be a version of the canonical token from another bridge + /// @param limit Limit on the balance of bridge token this contract could hold + /// @param hourlyLimit Limit on the hourly volume for this bridge + /// @param paused Whether swapping for this token should be paused or not + /// @param fee Fee taken upon swapping for or against this token + function addBridgeToken( + address bridgeToken, + uint256 limit, + uint256 hourlyLimit, + uint64 fee, + bool paused + ) external onlyGovernor { + _addBridgeToken(bridgeToken, limit, hourlyLimit, fee, paused); + } + + /// @notice Removes support for a token + /// @param bridgeToken Address of the bridge token to remove support for + function removeBridgeToken(address bridgeToken) external onlyGovernor { + if (IERC20(bridgeToken).balanceOf(address(this)) != 0) revert AssetStillControlledInReserves(); + delete bridges[bridgeToken]; + // Deletion from `bridgeTokensList` loop + uint256 bridgeTokensListLength = bridgeTokensList.length; + for (uint256 i = 0; i < bridgeTokensListLength - 1; i++) { + if (bridgeTokensList[i] == bridgeToken) { + // Replace the `bridgeToken` to remove with the last of the list + bridgeTokensList[i] = bridgeTokensList[bridgeTokensListLength - 1]; + break; + } + } + // Remove last element in array + bridgeTokensList.pop(); + emit BridgeTokenRemoved(bridgeToken); + } + + /// @notice Recovers any ERC20 token + /// @dev Can be used to withdraw bridge tokens for them to be de-bridged on mainnet + function recoverERC20(address tokenAddress, address to, uint256 amountToRecover) external onlyGovernor { + IERC20(tokenAddress).safeTransfer(to, amountToRecover); + emit Recovered(tokenAddress, to, amountToRecover); + } + + /// @notice Updates the `limit` amount for `bridgeToken` + function setLimit(address bridgeToken, uint256 limit) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bridges[bridgeToken].limit = limit; + emit BridgeTokenLimitUpdated(bridgeToken, limit); + } + + /// @notice Updates the `hourlyLimit` amount for `bridgeToken` + function setHourlyLimit(address bridgeToken, uint256 hourlyLimit) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bridges[bridgeToken].hourlyLimit = hourlyLimit; + emit BridgeTokenHourlyLimitUpdated(bridgeToken, hourlyLimit); + } + + /// @notice Updates the `chainTotalHourlyLimit` amount + function setChainTotalHourlyLimit(uint256 hourlyLimit) external onlyGovernorOrGuardian { + _setChainTotalHourlyLimit(hourlyLimit); + } + + /// @notice Updates the `fee` value for `bridgeToken` + function setSwapFee(address bridgeToken, uint64 fee) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + if (fee > BASE_PARAMS) revert TooHighParameterValue(); + bridges[bridgeToken].fee = fee; + emit BridgeTokenFeeUpdated(bridgeToken, fee); + } + + /// @notice Pauses or unpauses swapping in and out for a token + function toggleBridge(address bridgeToken) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bool pausedStatus = bridges[bridgeToken].paused; + bridges[bridgeToken].paused = !pausedStatus; + emit BridgeTokenToggled(bridgeToken, !pausedStatus); + } + + /// @notice Toggles fees for the address `theAddress` + function toggleFeesForAddress(address theAddress) external onlyGovernorOrGuardian { + uint256 feeExemptStatus = 1 - isFeeExempt[theAddress]; + isFeeExempt[theAddress] = feeExemptStatus; + emit FeeToggled(theAddress, feeExemptStatus); + } + + // ============================= INTERNAL FUNCTIONS ============================ + + /// @notice Internal version of the `addBridgeToken` function + function _addBridgeToken( + address bridgeToken, + uint256 limit, + uint256 hourlyLimit, + uint64 fee, + bool paused + ) internal { + if (bridges[bridgeToken].allowed || bridgeToken == address(0)) revert InvalidToken(); + if (fee > BASE_PARAMS) revert TooHighParameterValue(); + BridgeDetails memory _bridge; + _bridge.limit = limit; + _bridge.hourlyLimit = hourlyLimit; + _bridge.paused = paused; + _bridge.fee = fee; + _bridge.allowed = true; + bridges[bridgeToken] = _bridge; + bridgeTokensList.push(bridgeToken); + emit BridgeTokenAdded(bridgeToken, limit, hourlyLimit, fee, paused); + } + + /// @notice Internal version of the `setChainTotalHourlyLimit` + function _setChainTotalHourlyLimit(uint256 hourlyLimit) internal { + chainTotalHourlyLimit = hourlyLimit; + emit HourlyLimitUpdated(hourlyLimit); + } +} diff --git a/contracts/agToken/layerZero/LayerZeroBridge.sol b/contracts/agToken/layerZero/LayerZeroBridge.sol new file mode 100644 index 0000000..0aee30c --- /dev/null +++ b/contracts/agToken/layerZero/LayerZeroBridge.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.12; + +import "./utils/OFTCore.sol"; +import "oz-upgradeable/security/PausableUpgradeable.sol"; +import "oz/token/ERC20/extensions/draft-IERC20Permit.sol"; + +/// @title LayerZeroBridge +/// @author Angle Labs, Inc., forked from https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/token/oft/OFT.sol +/// @notice Contract to be deployed on Ethereum for bridging an AgToken using a bridge intermediate token and LayerZero +contract LayerZeroBridge is OFTCore, PausableUpgradeable { + /// @notice Name of the contract for indexing purposes + string public name; + + /// @notice Address of the bridgeable token + /// @dev Immutable + IERC20 public canonicalToken; + + /// @notice Maps an address to the amount of token bridged but not received + mapping(address => uint256) public balanceOf; + + // ================================ CONSTRUCTOR ================================ + /// @notice Initializes the contract + /// @param _name Name of the token corresponding to this contract + /// @param _lzEndpoint Layer zero endpoint to pass messages + /// @param _treasury Address of the treasury contract used for access control + function initialize(string memory _name, address _lzEndpoint, address _treasury) external initializer { + __LzAppUpgradeable_init(_lzEndpoint, _treasury); + name = _name; + canonicalToken = IERC20(address(ITreasury(_treasury).stablecoin())); + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + // ===================== EXTERNAL PERMISSIONLESS FUNCTIONS ===================== + + /// @inheritdoc OFTCore + function sendWithPermit( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes memory _adapterParams, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public payable override { + IERC20Permit(address(canonicalToken)).permit(msg.sender, address(this), _amount, deadline, v, r, s); + send(_dstChainId, _toAddress, _amount, _refundAddress, _zroPaymentAddress, _adapterParams); + } + + /// @inheritdoc OFTCore + function withdraw(uint256 amount, address recipient) external override returns (uint256) { + return _withdraw(amount, msg.sender, recipient); + } + + /// @notice Withdraws amount of `token` from the contract and sends it to the recipient + /// @param amount Amount to withdraw + /// @param recipient Address to withdraw for + /// @return The amount of canonical token sent + function withdrawFor(uint256 amount, address recipient) external returns (uint256) { + return _withdraw(amount, recipient, recipient); + } + + // ============================= INTERNAL FUNCTIONS ============================ + + /// @notice Withdraws `amount` from the balance of the `from` address and sends these tokens to the `to` address + /// @dev It's important to make sure that `from` is either the `msg.sender` or that `from` and `to` are the same + /// addresses + function _withdraw(uint256 amount, address from, address to) internal whenNotPaused returns (uint256) { + balanceOf[from] -= amount; // Will overflow if the amount is too big + canonicalToken.transfer(to, amount); + return amount; + } + + /// @inheritdoc OFTCore + function _debitFrom(uint16, bytes memory, uint256 _amount) internal override whenNotPaused returns (uint256) { + // No need to use safeTransferFrom as we know this implementation reverts on failure + canonicalToken.transferFrom(msg.sender, address(this), _amount); + return _amount; + } + + /// @inheritdoc OFTCore + function _debitCreditFrom(uint16, bytes memory, uint256 _amount) internal override whenNotPaused returns (uint256) { + balanceOf[msg.sender] -= _amount; + return _amount; + } + + /// @inheritdoc OFTCore + function _creditTo(uint16, address _toAddress, uint256 _amount) internal override whenNotPaused returns (uint256) { + // Should never revert as all the LayerZero bridge tokens come from + // this contract + uint256 balance = canonicalToken.balanceOf(address(this)); + if (balance < _amount) { + balanceOf[_toAddress] = _amount - balance; + if (balance != 0) canonicalToken.transfer(_toAddress, balance); + } else { + canonicalToken.transfer(_toAddress, _amount); + } + return _amount; + } + + // =============================== VIEW FUNCTIONS ============================== + + /// @inheritdoc ERC165Upgradeable + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IOFTCore).interfaceId || super.supportsInterface(interfaceId); + } + + // ============================ GOVERNANCE FUNCTIONS =========================== + + /// @notice Pauses bridging through the contract + /// @param pause Future pause status + function pauseSendTokens(bool pause) external onlyGovernorOrGuardian { + pause ? _pause() : _unpause(); + } + + /// @notice Decreases the balance of an address + /// @param amount Amount to withdraw from balance + /// @param recipient Address to withdraw from + function sweep(uint256 amount, address recipient) external onlyGovernorOrGuardian { + balanceOf[recipient] -= amount; // Will overflow if the amount is too big + } + + uint256[47] private __gap; +} diff --git a/contracts/agToken/layerZero/LayerZeroBridgeToken.sol b/contracts/agToken/layerZero/LayerZeroBridgeToken.sol new file mode 100644 index 0000000..5e11903 --- /dev/null +++ b/contracts/agToken/layerZero/LayerZeroBridgeToken.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.12; + +import "./utils/OFTCore.sol"; +import "../../interfaces/IAgTokenSideChainMultiBridge.sol"; +import "oz-upgradeable/security/PausableUpgradeable.sol"; +import "oz-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +/// @title LayerZeroBridgeToken +/// @author Angle Labs, Inc., forked from https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/token/oft/OFT.sol +/// @notice Contract to be deployed on a L2/sidechain for bridging an AgToken using a bridge intermediate token and LayerZero +contract LayerZeroBridgeToken is OFTCore, ERC20Upgradeable, PausableUpgradeable { + /// @notice Address of the bridgeable token + /// @dev Immutable + IAgTokenSideChainMultiBridge public canonicalToken; + + // =================================== ERROR =================================== + + error InvalidAllowance(); + + // ================================ CONSTRUCTOR ================================ + + /// @notice Initializes the contract + /// @param _name Name of the token corresponding to this contract + /// @param _symbol Symbol of the token corresponding to this contract + /// @param _lzEndpoint Layer zero endpoint to pass messages + /// @param _treasury Address of the treasury contract used for access control + /// @param initialSupply Initial supply to mint to the canonical token address + /// @dev The initial supply corresponds to the initial amount that could be bridged using this OFT + function initialize( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _treasury, + uint256 initialSupply + ) external initializer { + __ERC20_init_unchained(_name, _symbol); + __LzAppUpgradeable_init(_lzEndpoint, _treasury); + + canonicalToken = IAgTokenSideChainMultiBridge(address(ITreasury(_treasury).stablecoin())); + _approve(address(this), address(canonicalToken), type(uint256).max); + _mint(address(canonicalToken), initialSupply); + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + // ===================== EXTERNAL PERMISSIONLESS FUNCTIONS ===================== + + /// @inheritdoc OFTCore + function sendWithPermit( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes memory _adapterParams, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public payable override { + canonicalToken.permit(msg.sender, address(this), _amount, deadline, v, r, s); + send(_dstChainId, _toAddress, _amount, _refundAddress, _zroPaymentAddress, _adapterParams); + } + + /// @inheritdoc OFTCore + function withdraw(uint256 amount, address recipient) external override returns (uint256 amountMinted) { + // Does not check allowances as transfers from `msg.sender` + _transfer(msg.sender, address(this), amount); + amountMinted = canonicalToken.swapIn(address(this), amount, recipient); + uint256 leftover = balanceOf(address(this)); + if (leftover != 0) { + _transfer(address(this), msg.sender, leftover); + } + } + + // ============================= INTERNAL FUNCTIONS ============================ + + /// @inheritdoc OFTCore + function _debitFrom( + uint16, + bytes memory, + uint256 _amount + ) internal override whenNotPaused returns (uint256 amountSwapped) { + // No need to use safeTransferFrom as we know this implementation reverts on failure + canonicalToken.transferFrom(msg.sender, address(this), _amount); + + // Swap canonical for this bridge token. There may be some fees + amountSwapped = canonicalToken.swapOut(address(this), _amount, address(this)); + _burn(address(this), amountSwapped); + } + + /// @inheritdoc OFTCore + function _debitCreditFrom(uint16, bytes memory, uint256 _amount) internal override whenNotPaused returns (uint256) { + _burn(msg.sender, _amount); + return _amount; + } + + /// @inheritdoc OFTCore + function _creditTo( + uint16, + address _toAddress, + uint256 _amount + ) internal override whenNotPaused returns (uint256 amountMinted) { + _mint(address(this), _amount); + amountMinted = canonicalToken.swapIn(address(this), _amount, _toAddress); + uint256 leftover = balanceOf(address(this)); + if (leftover != 0) { + _transfer(address(this), _toAddress, leftover); + } + } + + // =============================== VIEW FUNCTIONS ============================== + + /// @inheritdoc ERC165Upgradeable + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IOFT).interfaceId || + interfaceId == type(IERC20).interfaceId || + super.supportsInterface(interfaceId); + } + + // ============================ GOVERNANCE FUNCTIONS =========================== + + /// @notice Mints the intermediate contract to the `canonicalToken` + /// @dev Used to increase the bridging capacity + function mint(uint256 amount) external onlyGovernorOrGuardian { + _mint(address(canonicalToken), amount); + } + + /// @notice Burns the intermediate contract from the `canonicalToken` + /// @dev Used to decrease the bridging capacity + function burn(uint256 amount) external onlyGovernorOrGuardian { + _burn(address(canonicalToken), amount); + } + + /// @notice Increases allowance of the `canonicalToken` + function setupAllowance() public onlyGovernorOrGuardian { + _approve(address(this), address(canonicalToken), type(uint256).max); + } + + /// @notice Pauses bridging through the contract + /// @param pause Future pause status + function pauseSendTokens(bool pause) external onlyGovernorOrGuardian { + pause ? _pause() : _unpause(); + } + + uint256[49] private __gap; +} diff --git a/contracts/agToken/layerZero/utils/IOFTCore.sol b/contracts/agToken/layerZero/utils/IOFTCore.sol new file mode 100644 index 0000000..61b9306 --- /dev/null +++ b/contracts/agToken/layerZero/utils/IOFTCore.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.12; + +import "oz/token/ERC20/IERC20.sol"; +import "oz/utils/introspection/IERC165.sol"; + +/** + * @dev Interface of the IOFT core standard + * @dev Forked from https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/token/oft/IOFTCore.sol + */ +interface IOFTCore is IERC165 { + /// @notice Estimates send token `_tokenId` to (`_dstChainId`, `_toAddress`) + /// @param _dstChainId L0 defined chain id to send tokens too + /// @param _toAddress dynamic bytes array which contains the address to whom you are sending + /// tokens to on the dstChain + /// @param _amount amount of the tokens to transfer + /// @param _useZro indicates to use zro to pay L0 fees + /// @param _adapterParams flexible bytes array to indicate messaging adapter services in L0 + function estimateSendFee( + uint16 _dstChainId, + bytes calldata _toAddress, + uint256 _amount, + bool _useZro, + bytes calldata _adapterParams + ) external view returns (uint256 nativeFee, uint256 zroFee); + + /// @notice Sends `_amount` amount of token to (`_dstChainId`, `_toAddress`) + /// @param _dstChainId the destination chain identifier + /// @param _toAddress can be any size depending on the `dstChainId`. + /// @param _amount the quantity of tokens in wei + /// @param _refundAddress the address LayerZero refunds if too much message fee is sent + /// @param _zroPaymentAddress set to address(0x0) if not paying in ZRO (LayerZero Token) + /// @param _adapterParams is a flexible bytes array to indicate messaging adapter services + function send( + uint16 _dstChainId, + bytes calldata _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes calldata _adapterParams + ) external payable; + + /// @notice Sends `_amount` amount of credit to (`_dstChainId`, `_toAddress`) + /// @param _dstChainId the destination chain identifier + /// @param _toAddress can be any size depending on the `dstChainId`. + /// @param _amount the quantity of credit to send in wei + /// @param _refundAddress the address LayerZero refunds if too much message fee is sent + /// @param _zroPaymentAddress set to address(0x0) if not paying in ZRO (LayerZero Token) + /// @param _adapterParams is a flexible bytes array to indicate messaging adapter services + function sendCredit( + uint16 _dstChainId, + bytes calldata _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes calldata _adapterParams + ) external payable; + + /// @notice Sends `_amount` amount of token to (`_dstChainId`, `_toAddress`) + /// @param _dstChainId The destination chain identifier + /// @param _toAddress Can be any size depending on the `dstChainId`. + /// @param _amount Quantity of tokens in wei + /// @param _refundAddress Address LayerZero refunds if too much message fee is sent + /// @param _zroPaymentAddress Set to address(0x0) if not paying in ZRO (LayerZero Token) + /// @param _adapterParams Flexible bytes array to indicate messaging adapter services + /// @param deadline Deadline parameter for the signature to be valid + /// @dev The `v`, `r`, and `s` parameters are used as signature data + function sendWithPermit( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes memory _adapterParams, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external payable; + + /// @notice Withdraws amount of canonical token from the `msg.sender` balance and sends it to the recipient + /// @param amount Amount to withdraw + /// @param recipient Address to send the canonical token to + /// @return The amount of canonical token sent + function withdraw(uint256 amount, address recipient) external returns (uint256); + + /// @dev Emitted when `_amount` tokens are moved from the `_sender` to (`_dstChainId`, `_toAddress`) + /// `_nonce` is the outbound nonce + event SendToChain( + address indexed _sender, + uint16 indexed _dstChainId, + bytes indexed _toAddress, + uint256 _amount, + uint64 _nonce + ); + + /// @dev Emitted when `_amount` tokens are received from `_srcChainId` into the `_toAddress` on the local chain. + /// `_nonce` is the inbound nonce. + event ReceiveFromChain( + uint16 indexed _srcChainId, + bytes indexed _srcAddress, + address indexed _toAddress, + uint256 _amount, + uint64 _nonce + ); +} + +/// @dev Interface of the OFT standard +interface IOFT is IOFTCore, IERC20 {} diff --git a/contracts/agToken/layerZero/utils/NonblockingLzApp.sol b/contracts/agToken/layerZero/utils/NonblockingLzApp.sol new file mode 100644 index 0000000..9390e0f --- /dev/null +++ b/contracts/agToken/layerZero/utils/NonblockingLzApp.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.12; + +import "oz-upgradeable/proxy/utils/Initializable.sol"; +import "layer-zero/interfaces/ILayerZeroReceiver.sol"; +import "layer-zero/interfaces/ILayerZeroUserApplicationConfig.sol"; +import "layer-zero/interfaces/ILayerZeroEndpoint.sol"; +import "../../../interfaces/ITreasury.sol"; + +/// @title NonblockingLzApp +/// @author Angle Labs, Inc., forked from https://github.com/LayerZero-Labs/solidity-examples/ +/// @notice Base contract for bridging using LayerZero +abstract contract NonblockingLzApp is Initializable, ILayerZeroReceiver, ILayerZeroUserApplicationConfig { + /// @notice Layer Zero endpoint + ILayerZeroEndpoint public lzEndpoint; + + /// @notice Maps chainIds to failed messages to retry them + mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) public failedMessages; + + /// @notice Maps chainIds to their OFT address + mapping(uint16 => bytes) public trustedRemoteLookup; + + /// @notice Reference to the treasury contract to fetch access control + address public treasury; + + /// @notice Maps pairs of (`to` chain, `packetType`) to the minimum amount of gas needed on the destination chain + mapping(uint16 => mapping(uint16 => uint256)) public minDstGasLookup; + + /// @notice For future LayerZero compatibility + address public precrime; + + // ================================== Events =================================== + + event SetTrustedRemote(uint16 _srcChainId, bytes _srcAddress); + event MessageFailed(uint16 _srcChainId, bytes _srcAddress, uint64 _nonce, bytes _payload); + + // =============================== Errors ================================ + + error NotGovernor(); + error NotGovernorOrGuardian(); + error InsufficientGas(); + error InvalidEndpoint(); + error InvalidSource(); + error InvalidCaller(); + error InvalidParams(); + error InvalidPayload(); + error ZeroAddress(); + + // ============================= Constructor =================================== + + //solhint-disable-next-line + function __LzAppUpgradeable_init(address _endpoint, address _treasury) internal { + if (_endpoint == address(0) || _treasury == address(0)) revert ZeroAddress(); + lzEndpoint = ILayerZeroEndpoint(_endpoint); + treasury = _treasury; + } + + // =============================== Modifiers =================================== + + /// @notice Checks whether the `msg.sender` has the governor role or the guardian role + modifier onlyGovernorOrGuardian() { + if (!ITreasury(treasury).isGovernorOrGuardian(msg.sender)) revert NotGovernorOrGuardian(); + _; + } + + // ==================== External Permissionless Functions ====================== + + /// @notice Receives a message from the LZ endpoint and process it + /// @param _srcChainId ChainId of the source chain - LayerZero standard + /// @param _srcAddress Sender of the source chain + /// @param _nonce Nounce of the message + /// @param _payload Data: recipient address and amount + function lzReceive( + uint16 _srcChainId, + bytes memory _srcAddress, + uint64 _nonce, + bytes memory _payload + ) public virtual override { + // lzReceive must be called by the endpoint for security + if (msg.sender != address(lzEndpoint)) revert InvalidEndpoint(); + + bytes memory trustedRemote = trustedRemoteLookup[_srcChainId]; + // if will still block the message pathway from (srcChainId, srcAddress). + // should not receive message from untrusted remote. + if (_srcAddress.length != trustedRemote.length || keccak256(_srcAddress) != keccak256(trustedRemote)) + revert InvalidSource(); + + _blockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); + } + + /// @notice Retries a message that previously failed and was stored + /// @param _srcChainId ChainId of the source chain - LayerZero standard + /// @param _srcAddress Sender of the source chain + /// @param _nonce Nounce of the message + /// @param _payload Data: recipient address and amount + function retryMessage( + uint16 _srcChainId, + bytes memory _srcAddress, + uint64 _nonce, + bytes memory _payload + ) public payable virtual { + // assert there is message to retry + bytes32 payloadHash = failedMessages[_srcChainId][_srcAddress][_nonce]; + if (payloadHash == bytes32(0) || keccak256(_payload) != payloadHash) revert InvalidPayload(); + // clear the stored message + failedMessages[_srcChainId][_srcAddress][_nonce] = bytes32(0); + // execute the message. revert if it fails again + _nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); + } + + // ============================= Internal Functions =================================== + + /// @notice Handles message receptions in a non blocking way + /// @param _srcChainId ChainId of the source chain - LayerZero standard + /// @param _srcAddress Sender of the source chain + /// @param _nonce Nounce of the message + /// @param _payload Data: recipient address and amount + /// @dev public for the needs of try / catch but effectively internal + function nonblockingLzReceive( + uint16 _srcChainId, + bytes memory _srcAddress, + uint64 _nonce, + bytes memory _payload + ) public virtual { + // only internal transaction + if (msg.sender != address(this)) revert InvalidCaller(); + _nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); + } + + /// @notice Handles message receptions in a non blocking way + /// @param _srcChainId ChainId of the source chain - LayerZero standard + /// @param _srcAddress Sender of the source chain + /// @param _nonce Nounce of the message + /// @param _payload Data: recipient address and amount + function _nonblockingLzReceive( + uint16 _srcChainId, + bytes memory _srcAddress, + uint64 _nonce, + bytes memory _payload + ) internal virtual; + + /// @notice Handles message receptions in a blocking way + /// @param _srcChainId ChainId of the source chain - LayerZero standard + /// @param _srcAddress Sender of the source chain + /// @param _nonce Nounce of the message + /// @param _payload Data: recipient address and amount + function _blockingLzReceive( + uint16 _srcChainId, + bytes memory _srcAddress, + uint64 _nonce, + bytes memory _payload + ) internal { + // try-catch all errors/exceptions + try this.nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload) { + // do nothing + } catch { + // error / exception + failedMessages[_srcChainId][_srcAddress][_nonce] = keccak256(_payload); + emit MessageFailed(_srcChainId, _srcAddress, _nonce, _payload); + } + } + + /// @notice Sends a message to the LZ endpoint and process it + /// @param _dstChainId L0 defined chain id to send tokens too + /// @param _payload Data: recipient address and amount + /// @param _refundAddress Address LayerZero refunds if too much message fee is sent + /// @param _zroPaymentAddress Set to address(0x0) if not paying in ZRO (LayerZero Token) + /// @param _adapterParams Flexible bytes array to indicate messaging adapter services in L0 + function _lzSend( + uint16 _dstChainId, + bytes memory _payload, + address payable _refundAddress, + address _zroPaymentAddress, + bytes memory _adapterParams + ) internal virtual { + bytes memory trustedRemote = trustedRemoteLookup[_dstChainId]; + if (trustedRemote.length == 0) revert InvalidSource(); + //solhint-disable-next-line + lzEndpoint.send{ value: msg.value }( + _dstChainId, + trustedRemote, + _payload, + _refundAddress, + _zroPaymentAddress, + _adapterParams + ); + } + + /// @notice Checks the gas limit of a given transaction + function _checkGasLimit( + uint16 _dstChainId, + uint16 _type, + bytes memory _adapterParams, + uint256 _extraGas + ) internal view virtual { + uint256 minGasLimit = minDstGasLookup[_dstChainId][_type] + _extraGas; + if (minGasLimit == 0 || minGasLimit > _getGasLimit(_adapterParams)) revert InsufficientGas(); + } + + /// @notice Gets the gas limit from the `_adapterParams` parameter + function _getGasLimit(bytes memory _adapterParams) internal pure virtual returns (uint256 gasLimit) { + if (_adapterParams.length < 34) revert InvalidParams(); + // solhint-disable-next-line + assembly { + gasLimit := mload(add(_adapterParams, 34)) + } + } + + // ======================= Governance Functions ================================ + + /// @notice Sets the corresponding address on an other chain. + /// @param _srcChainId ChainId of the source chain - LayerZero standard + /// @param _srcAddress Address on the source chain + /// @dev Used for both receiving and sending message + /// @dev There can only be one trusted source per chain + /// @dev Allows owner to set it multiple times. + function setTrustedRemote(uint16 _srcChainId, bytes calldata _srcAddress) external onlyGovernorOrGuardian { + trustedRemoteLookup[_srcChainId] = _srcAddress; + emit SetTrustedRemote(_srcChainId, _srcAddress); + } + + /// @notice Fetches the default LZ config + function getConfig( + uint16 _version, + uint16 _chainId, + address, + uint256 _configType + ) external view returns (bytes memory) { + return lzEndpoint.getConfig(_version, _chainId, address(this), _configType); + } + + /// @notice Overrides the default LZ config + function setConfig( + uint16 _version, + uint16 _chainId, + uint256 _configType, + bytes calldata _config + ) external override onlyGovernorOrGuardian { + lzEndpoint.setConfig(_version, _chainId, _configType, _config); + } + + /// @notice Overrides the default LZ config + function setSendVersion(uint16 _version) external override onlyGovernorOrGuardian { + lzEndpoint.setSendVersion(_version); + } + + /// @notice Overrides the default LZ config + function setReceiveVersion(uint16 _version) external override onlyGovernorOrGuardian { + lzEndpoint.setReceiveVersion(_version); + } + + /// @notice Unpauses the receive functionalities + function forceResumeReceive( + uint16 _srcChainId, + bytes calldata _srcAddress + ) external override onlyGovernorOrGuardian { + lzEndpoint.forceResumeReceive(_srcChainId, _srcAddress); + } + + /// @notice Sets the minimum gas parameter for a packet type on a given chain + function setMinDstGas(uint16 _dstChainId, uint16 _packetType, uint256 _minGas) external onlyGovernorOrGuardian { + if (_minGas == 0) revert InvalidParams(); + minDstGasLookup[_dstChainId][_packetType] = _minGas; + } + + /// @notice Sets the precrime variable + function setPrecrime(address _precrime) external onlyGovernorOrGuardian { + precrime = _precrime; + } + + // ======================= View Functions ================================ + + /// @notice Checks if the `_srcAddress` corresponds to the trusted source + function isTrustedRemote(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool) { + bytes memory trustedSource = trustedRemoteLookup[_srcChainId]; + return keccak256(trustedSource) == keccak256(_srcAddress); + } + + uint256[44] private __gap; +} diff --git a/contracts/agToken/layerZero/utils/OFTCore.sol b/contracts/agToken/layerZero/utils/OFTCore.sol new file mode 100644 index 0000000..ec7641d --- /dev/null +++ b/contracts/agToken/layerZero/utils/OFTCore.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.12; + +import "./NonblockingLzApp.sol"; +import "./IOFTCore.sol"; +import "oz-upgradeable/utils/introspection/ERC165Upgradeable.sol"; + +/// @title OFTCore +/// @author Forked from https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/token/oft/OFTCore.sol +/// but with slight modifications from the Angle Labs, Inc. which added return values to the `_creditTo` +/// and `_debitFrom` functions +/// @notice Base contract for bridging using LayerZero +abstract contract OFTCore is NonblockingLzApp, ERC165Upgradeable, IOFTCore { + /// @notice Amount of additional gas specified + uint256 public constant EXTRA_GAS = 200000; + /// @notice Packet type for token transfer + uint16 public constant PT_SEND = 0; + + /// @notice Whether to use custom parameters in transactions + uint8 public useCustomAdapterParams; + + // ==================== External Permissionless Functions ====================== + + /// @inheritdoc IOFTCore + function sendWithPermit( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes memory _adapterParams, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public payable virtual; + + /// @inheritdoc IOFTCore + function send( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes memory _adapterParams + ) public payable virtual { + _checkAdapterParams(_dstChainId, PT_SEND, _adapterParams, EXTRA_GAS); + _amount = _debitFrom(_dstChainId, _toAddress, _amount); + + bytes memory payload = abi.encode(_toAddress, _amount); + _lzSend(_dstChainId, payload, _refundAddress, _zroPaymentAddress, _adapterParams); + + uint64 nonce = lzEndpoint.getOutboundNonce(_dstChainId, address(this)); + emit SendToChain(msg.sender, _dstChainId, _toAddress, _amount, nonce); + } + + /// @inheritdoc IOFTCore + function sendCredit( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes memory _adapterParams + ) public payable virtual { + _checkAdapterParams(_dstChainId, PT_SEND, _adapterParams, EXTRA_GAS); + _amount = _debitCreditFrom(_dstChainId, _toAddress, _amount); + + _send(_dstChainId, _toAddress, _amount, _refundAddress, _zroPaymentAddress, _adapterParams); + } + + /// @inheritdoc IOFTCore + function withdraw(uint256 amount, address recipient) external virtual returns (uint256); + + /// @notice Sets whether custom adapter parameters can be used or not + function setUseCustomAdapterParams(uint8 _useCustomAdapterParams) public virtual onlyGovernorOrGuardian { + useCustomAdapterParams = _useCustomAdapterParams; + } + + // =========================== Internal Functions ============================== + + /// @notice Internal function to send `_amount` amount of token to (`_dstChainId`, `_toAddress`) + /// @param _dstChainId the destination chain identifier + /// @param _toAddress can be any size depending on the `dstChainId`. + /// @param _amount the quantity of tokens in wei + /// @param _refundAddress the address LayerZero refunds if too much message fee is sent + /// @param _zroPaymentAddress set to address(0x0) if not paying in ZRO (LayerZero Token) + /// @param _adapterParams is a flexible bytes array to indicate messaging adapter services + /// @dev Accounting and checks should be performed beforehand + function _send( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount, + address payable _refundAddress, + address _zroPaymentAddress, + bytes memory _adapterParams + ) internal { + bytes memory payload = abi.encode(_toAddress, _amount); + _lzSend(_dstChainId, payload, _refundAddress, _zroPaymentAddress, _adapterParams); + + uint64 nonce = lzEndpoint.getOutboundNonce(_dstChainId, address(this)); + emit SendToChain(msg.sender, _dstChainId, _toAddress, _amount, nonce); + } + + /// @inheritdoc NonblockingLzApp + function _nonblockingLzReceive( + uint16 _srcChainId, + bytes memory _srcAddress, + uint64 _nonce, + bytes memory _payload + ) internal virtual override { + // decode and load the toAddress + (bytes memory toAddressBytes, uint256 amount) = abi.decode(_payload, (bytes, uint256)); + address toAddress; + //solhint-disable-next-line + assembly { + toAddress := mload(add(toAddressBytes, 20)) + } + amount = _creditTo(_srcChainId, toAddress, amount); + + emit ReceiveFromChain(_srcChainId, _srcAddress, toAddress, amount, _nonce); + } + + /// @notice Checks the adapter parameters given during the smart contract call + function _checkAdapterParams( + uint16 _dstChainId, + uint16 _pkType, + bytes memory _adapterParams, + uint256 _extraGas + ) internal virtual { + if (useCustomAdapterParams > 0) _checkGasLimit(_dstChainId, _pkType, _adapterParams, _extraGas); + else if (_adapterParams.length != 0) revert InvalidParams(); + } + + /// @notice Makes accountability when bridging from this contract using canonical token + /// @param _dstChainId ChainId of the destination chain - LayerZero standard + /// @param _toAddress Recipient on the destination chain + /// @param _amount Amount to bridge + function _debitFrom( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount + ) internal virtual returns (uint256); + + /// @notice Makes accountability when bridging from this contract's credit + /// @param _dstChainId ChainId of the destination chain - LayerZero standard + /// @param _toAddress Recipient on the destination chain + /// @param _amount Amount to bridge + function _debitCreditFrom( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount + ) internal virtual returns (uint256); + + /// @notice Makes accountability when bridging to this contract + /// @param _srcChainId ChainId of the source chain - LayerZero standard + /// @param _toAddress Recipient on this chain + /// @param _amount Amount to bridge + function _creditTo(uint16 _srcChainId, address _toAddress, uint256 _amount) internal virtual returns (uint256); + + // ========================== View Functions =================================== + + /// @inheritdoc ERC165Upgradeable + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC165Upgradeable, IERC165) returns (bool) { + return interfaceId == type(IOFTCore).interfaceId || super.supportsInterface(interfaceId); + } + + /// @inheritdoc IOFTCore + function estimateSendFee( + uint16 _dstChainId, + bytes memory _toAddress, + uint256 _amount, + bool _useZro, + bytes memory _adapterParams + ) public view virtual override returns (uint256 nativeFee, uint256 zroFee) { + // mock the payload for send() + bytes memory payload = abi.encode(_toAddress, _amount); + return lzEndpoint.estimateFees(_dstChainId, address(this), payload, _useZro, _adapterParams); + } + + uint256[49] private __gap; +} diff --git a/contracts/agToken/nameable/AgEURNameable.sol b/contracts/agToken/nameable/AgEURNameable.sol new file mode 100644 index 0000000..b8cf714 --- /dev/null +++ b/contracts/agToken/nameable/AgEURNameable.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "../AgEUR.sol"; + +/// @title AgEURNameable +/// @author Angle Labs, Inc. +contract AgEURNameable is AgEUR { + /// @notice Checks whether the `msg.sender` has the governor role or not + modifier onlyGovernor() { + if (!ITreasury(treasury).isGovernor(msg.sender)) revert NotGovernor(); + _; + } + + string internal __name; + + string internal __symbol; + + uint256[48] private __gapNameable; + + /// @inheritdoc ERC20Upgradeable + function name() public view override returns (string memory) { + return __name; + } + + /// @inheritdoc ERC20Upgradeable + function symbol() public view override returns (string memory) { + return __symbol; + } + + /// @notice Updates the name and symbol of the token + function setNameAndSymbol(string memory newName, string memory newSymbol) external onlyGovernor { + __name = newName; + __symbol = newSymbol; + } +} diff --git a/contracts/agToken/nameable/AgTokenNameable.sol b/contracts/agToken/nameable/AgTokenNameable.sol new file mode 100644 index 0000000..783c37d --- /dev/null +++ b/contracts/agToken/nameable/AgTokenNameable.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "../AgToken.sol"; + +/// @title AgTokenNameable +/// @author Angle Labs, Inc. +contract AgTokenNameable is AgToken { + error NotGovernor(); + + /// @notice Checks whether the `msg.sender` has the governor role or not + modifier onlyGovernor() { + if (!ITreasury(treasury).isGovernor(msg.sender)) revert NotGovernor(); + _; + } + string internal __name; + + string internal __symbol; + + uint256[48] private __gapNameable; + + /// @inheritdoc ERC20Upgradeable + function name() public view override returns (string memory) { + return __name; + } + + /// @inheritdoc ERC20Upgradeable + function symbol() public view override returns (string memory) { + return __symbol; + } + + /// @notice Updates the name and symbol of the token + function setNameAndSymbol(string memory newName, string memory newSymbol) external onlyGovernor { + __name = newName; + __symbol = newSymbol; + } +} diff --git a/contracts/agToken/nameable/AgTokenSideChainMultiBridgeNameable.sol b/contracts/agToken/nameable/AgTokenSideChainMultiBridgeNameable.sol new file mode 100644 index 0000000..ff00a6c --- /dev/null +++ b/contracts/agToken/nameable/AgTokenSideChainMultiBridgeNameable.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "../AgTokenSideChainMultiBridge.sol"; + +/// @title AgTokenSideChainMultiBridgeNameable +/// @author Angle Labs, Inc. +contract AgTokenSideChainMultiBridgeNameable is AgTokenSideChainMultiBridge { + string internal __name; + + string internal __symbol; + + uint256[48] private __gapNameable; + + /// @inheritdoc ERC20Upgradeable + function name() public view override returns (string memory) { + return __name; + } + + /// @inheritdoc ERC20Upgradeable + function symbol() public view override returns (string memory) { + return __symbol; + } + + /// @notice Updates the name and symbol of the token + function setNameAndSymbol(string memory newName, string memory newSymbol) external onlyGovernor { + __name = newName; + __symbol = newSymbol; + } +} diff --git a/contracts/agToken/nameable/TokenPolygonUpgradeableNameable.sol b/contracts/agToken/nameable/TokenPolygonUpgradeableNameable.sol new file mode 100644 index 0000000..948c67b --- /dev/null +++ b/contracts/agToken/nameable/TokenPolygonUpgradeableNameable.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "../polygon/TokenPolygonUpgradeable.sol"; + +/// @title TokenPolygonUpgradeableNameable +/// @author Angle Labs, Inc. +contract TokenPolygonUpgradeableNameable is TokenPolygonUpgradeable { + string internal __name; + + string internal __symbol; + + uint256[48] private __gapNameable; + + /// @inheritdoc ERC20UpgradeableCustom + function name() public view override returns (string memory) { + return __name; + } + + /// @inheritdoc ERC20UpgradeableCustom + function symbol() public view override returns (string memory) { + return __symbol; + } + + /// @notice Updates the name and symbol of the token + function setNameAndSymbol(string memory newName, string memory newSymbol) external onlyGovernor { + __name = newName; + __symbol = newSymbol; + } +} diff --git a/contracts/agToken/polygon/TokenPolygonUpgradeable.sol b/contracts/agToken/polygon/TokenPolygonUpgradeable.sol new file mode 100644 index 0000000..14b627e --- /dev/null +++ b/contracts/agToken/polygon/TokenPolygonUpgradeable.sol @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "./utils/ERC20UpgradeableCustom.sol"; +import "oz-upgradeable/access/AccessControlUpgradeable.sol"; +import "oz-upgradeable/proxy/utils/Initializable.sol"; +import "oz-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; +import "oz/token/ERC20/IERC20.sol"; +import "oz/token/ERC20/utils/SafeERC20.sol"; + +import "../../interfaces/IAgToken.sol"; +import "../../interfaces/ITreasury.sol"; + +interface IChildToken { + function deposit(address user, bytes calldata depositData) external; + + function withdraw(uint256 amount) external; +} + +contract TokenPolygonUpgradeable is + Initializable, + ERC20UpgradeableCustom, + AccessControlUpgradeable, + EIP712Upgradeable, + IChildToken +{ + bytes32 public constant DEPOSITOR_ROLE = keccak256("DEPOSITOR_ROLE"); + + /// @dev Emitted when the child chain manager changes + event ChildChainManagerAdded(address newAddress); + event ChildChainManagerRevoked(address oldAddress); + + constructor() initializer {} + + function initialize( + string memory _name, + string memory _symbol, + address childChainManager, + address guardian + ) public initializer { + __ERC20_init(_name, _symbol); + __AccessControl_init(); + _setupRole(DEFAULT_ADMIN_ROLE, guardian); + _setupRole(DEPOSITOR_ROLE, childChainManager); + __EIP712_init(_name, "1"); + } + + /** + * @notice Called when the bridge has tokens to mint + * @param user Address to mint the token to + * @param depositData Encoded amount to mint + */ + function deposit(address user, bytes calldata depositData) external override { + require(hasRole(DEPOSITOR_ROLE, msg.sender)); + uint256 amount = abi.decode(depositData, (uint256)); + _mint(user, amount); + } + + /** + * @notice Called when user wants to withdraw tokens back to root chain + * @dev Should burn user's tokens. This transaction will be verified when exiting on root chain + * @param amount Amount of tokens to withdraw + */ + function withdraw(uint256 amount) external override { + _burn(_msgSender(), amount); + } + + // ============================================================================= + // ======================= New data added for the upgrade ====================== + // ============================================================================= + + mapping(address => bool) public isMinter; + /// @notice Reference to the treasury contract which can grant minting rights + address public treasury; + /// @notice Boolean to check whether the contract has been reinitialized after its upgrade + bool public treasuryInitialized; + + using SafeERC20 for IERC20; + + /// @notice Base used for fee computation + uint256 public constant BASE_PARAMS = 10 ** 9; + + // =============================== Bridging Data =============================== + + /// @notice Struct with some data about a specific bridge token + struct BridgeDetails { + // Limit on the balance of bridge token held by the contract: it is designed + // to reduce the exposure of the system to hacks + uint256 limit; + // Limit on the hourly volume of token minted through this bridge + // Technically the limit over a rolling hour is hourlyLimit x2 as hourly limit + // is enforced only between x:00 and x+1:00 + uint256 hourlyLimit; + // Fee taken for swapping in and out the token + uint64 fee; + // Whether the associated token is allowed or not + bool allowed; + // Whether swapping in and out from the associated token is paused or not + bool paused; + } + + /// @notice Maps a bridge token to data + mapping(address => BridgeDetails) public bridges; + /// @notice List of all bridge tokens + address[] public bridgeTokensList; + /// @notice Maps a bridge token to the associated hourly volume + mapping(address => mapping(uint256 => uint256)) public usage; + /// @notice Maps an address to whether it is exempt of fees for when it comes to swapping in and out + mapping(address => uint256) public isFeeExempt; + /// @notice Limit to the amount of tokens that can be sent from that chain to another chain + uint256 public chainTotalHourlyLimit; + /// @notice Usage per hour on that chain. Maps an hourly timestamp to the total volume swapped out on the chain + mapping(uint256 => uint256) public chainTotalUsage; + + uint256[42] private __gap; + + // ================================== Events =================================== + + event BridgeTokenAdded(address indexed bridgeToken, uint256 limit, uint256 hourlyLimit, uint64 fee, bool paused); + event BridgeTokenToggled(address indexed bridgeToken, bool toggleStatus); + event BridgeTokenRemoved(address indexed bridgeToken); + event BridgeTokenFeeUpdated(address indexed bridgeToken, uint64 fee); + event BridgeTokenLimitUpdated(address indexed bridgeToken, uint256 limit); + event BridgeTokenHourlyLimitUpdated(address indexed bridgeToken, uint256 hourlyLimit); + event HourlyLimitUpdated(uint256 hourlyLimit); + event FeeToggled(address indexed theAddress, uint256 toggleStatus); + event KeeperToggled(address indexed keeper, bool toggleStatus); + event MinterToggled(address indexed minter); + event Recovered(address indexed token, address indexed to, uint256 amount); + event TreasuryUpdated(address indexed _treasury); + + // ================================== Errors =================================== + + error AssetStillControlledInReserves(); + error BurnAmountExceedsAllowance(); + error HourlyLimitExceeded(); + error InvalidSender(); + error InvalidToken(); + error InvalidTreasury(); + error NotGovernor(); + error NotGovernorOrGuardian(); + error NotMinter(); + error NotTreasury(); + error TooBigAmount(); + error TooHighParameterValue(); + error TreasuryAlreadyInitialized(); + error ZeroAddress(); + + /// @notice Checks to see if it is the `Treasury` calling this contract + /// @dev There is no Access Control here, because it can be handled cheaply through this modifier + modifier onlyTreasury() { + if (msg.sender != treasury) revert NotTreasury(); + _; + } + + /// @notice Checks whether the sender has the minting right + modifier onlyMinter() { + if (!isMinter[msg.sender]) revert NotMinter(); + _; + } + + /// @notice Checks whether the `msg.sender` has the governor role or not + modifier onlyGovernor() { + if (!ITreasury(treasury).isGovernor(msg.sender)) revert NotGovernor(); + _; + } + + /// @notice Checks whether the `msg.sender` has the governor role or the guardian role + modifier onlyGovernorOrGuardian() { + if (!ITreasury(treasury).isGovernorOrGuardian(msg.sender)) revert NotGovernorOrGuardian(); + _; + } + + /// @notice Sets up the treasury contract on Polygon after the upgrade + /// @param _treasury Address of the treasury contract + function setUpTreasury(address _treasury) external { + // Only governor on Polygon + if (msg.sender != 0xdA2D2f638D6fcbE306236583845e5822554c02EA) revert NotGovernor(); + if (address(ITreasury(_treasury).stablecoin()) != address(this)) revert InvalidTreasury(); + if (treasuryInitialized) revert TreasuryAlreadyInitialized(); + treasury = _treasury; + treasuryInitialized = true; + emit TreasuryUpdated(_treasury); + } + + // =========================== External Function =============================== + + /// @notice Allows anyone to burn agToken without redeeming collateral back + /// @param amount Amount of stablecoins to burn + /// @dev This function can typically be called if there is a settlement mechanism to burn stablecoins + function burnStablecoin(uint256 amount) external { + _burnCustom(msg.sender, amount); + } + + // ======================= Minter Role Only Functions ========================== + + function burnSelf(uint256 amount, address burner) external onlyMinter { + _burnCustom(burner, amount); + } + + function burnFrom(uint256 amount, address burner, address sender) external onlyMinter { + _burnFromNoRedeem(amount, burner, sender); + } + + function mint(address account, uint256 amount) external onlyMinter { + _mint(account, amount); + } + + // ======================= Treasury Only Functions ============================= + + function addMinter(address minter) external onlyTreasury { + isMinter[minter] = true; + emit MinterToggled(minter); + } + + function removeMinter(address minter) external { + if (msg.sender != address(treasury) && msg.sender != minter) revert InvalidSender(); + isMinter[minter] = false; + emit MinterToggled(minter); + } + + function setTreasury(address _treasury) external onlyTreasury { + treasury = _treasury; + emit TreasuryUpdated(_treasury); + } + + // ============================ Internal Function ============================== + + /// @notice Internal version of the function `burnFromNoRedeem` + /// @param amount Amount to burn + /// @dev It is at the level of this function that allowance checks are performed + function _burnFromNoRedeem(uint256 amount, address burner, address sender) internal { + if (burner != sender) { + uint256 currentAllowance = allowance(burner, sender); + if (currentAllowance < amount) revert BurnAmountExceedsAllowance(); + _approve(burner, sender, currentAllowance - amount); + } + _burnCustom(burner, amount); + } + + // ==================== External Permissionless Functions ====================== + + /// @notice Returns the list of all supported bridge tokens + /// @dev Helpful for UIs + function allBridgeTokens() external view returns (address[] memory) { + return bridgeTokensList; + } + + /// @notice Returns the current volume for a bridge, for the current hour + /// @dev Helpful for UIs + function currentUsage(address bridge) external view returns (uint256) { + return usage[bridge][block.timestamp / 3600]; + } + + /// @notice Returns the current total volume on the chain for the current hour + /// @dev Helpful for UIs + function currentTotalUsage() external view returns (uint256) { + return chainTotalUsage[block.timestamp / 3600]; + } + + /// @notice Mints the canonical token from a supported bridge token + /// @param bridgeToken Bridge token to use to mint + /// @param amount Amount of bridge tokens to send + /// @param to Address to which the stablecoin should be sent + /// @dev Some fees may be taken by the protocol depending on the token used and on the address calling + function swapIn(address bridgeToken, uint256 amount, address to) external returns (uint256) { + BridgeDetails memory bridgeDetails = bridges[bridgeToken]; + if (!bridgeDetails.allowed || bridgeDetails.paused) revert InvalidToken(); + uint256 balance = IERC20(bridgeToken).balanceOf(address(this)); + if (balance + amount > bridgeDetails.limit) { + // In case someone maliciously sends tokens to this contract + // Or the limit changes + if (bridgeDetails.limit > balance) amount = bridgeDetails.limit - balance; + else { + amount = 0; + } + } + + // Checking requirement on the hourly volume + uint256 hour = block.timestamp / 3600; + uint256 hourlyUsage = usage[bridgeToken][hour] + amount; + if (hourlyUsage > bridgeDetails.hourlyLimit) { + // Edge case when the hourly limit changes + if (bridgeDetails.hourlyLimit > usage[bridgeToken][hour]) + amount = bridgeDetails.hourlyLimit - usage[bridgeToken][hour]; + else { + amount = 0; + } + } + usage[bridgeToken][hour] += amount; + + IERC20(bridgeToken).safeTransferFrom(msg.sender, address(this), amount); + uint256 canonicalOut = amount; + // Computing fees + if (isFeeExempt[msg.sender] == 0) { + canonicalOut -= (canonicalOut * bridgeDetails.fee) / BASE_PARAMS; + } + _mint(to, canonicalOut); + return canonicalOut; + } + + /// @notice Burns the canonical token in exchange for a bridge token + /// @param bridgeToken Bridge token required + /// @param amount Amount of canonical tokens to burn + /// @param to Address to which the bridge token should be sent + /// @dev Some fees may be taken by the protocol depending on the token used and on the address calling + function swapOut(address bridgeToken, uint256 amount, address to) external returns (uint256) { + BridgeDetails memory bridgeDetails = bridges[bridgeToken]; + if (!bridgeDetails.allowed || bridgeDetails.paused) revert InvalidToken(); + + uint256 hour = block.timestamp / 3600; + uint256 hourlyUsage = chainTotalUsage[hour] + amount; + // If the amount being swapped out exceeds the limit, we revert + // We don't want to change the amount being swapped out. + // The user can decide to send another tx with the correct amount to reach the limit + if (hourlyUsage > chainTotalHourlyLimit) revert HourlyLimitExceeded(); + chainTotalUsage[hour] = hourlyUsage; + + _burnCustom(msg.sender, amount); + uint256 bridgeOut = amount; + if (isFeeExempt[msg.sender] == 0) { + bridgeOut -= (bridgeOut * bridgeDetails.fee) / BASE_PARAMS; + } + IERC20(bridgeToken).safeTransfer(to, bridgeOut); + return bridgeOut; + } + + // ======================= Governance Functions ================================ + + /// @notice Adds support for a bridge token + /// @param bridgeToken Bridge token to add: it should be a version of the stablecoin from another bridge + /// @param limit Limit on the balance of bridge token this contract could hold + /// @param hourlyLimit Limit on the hourly volume for this bridge + /// @param paused Whether swapping for this token should be paused or not + /// @param fee Fee taken upon swapping for or against this token + function addBridgeToken( + address bridgeToken, + uint256 limit, + uint256 hourlyLimit, + uint64 fee, + bool paused + ) external onlyGovernor { + if (bridges[bridgeToken].allowed || bridgeToken == address(0)) revert InvalidToken(); + if (fee > BASE_PARAMS) revert TooHighParameterValue(); + BridgeDetails memory _bridge; + _bridge.limit = limit; + _bridge.hourlyLimit = hourlyLimit; + _bridge.paused = paused; + _bridge.fee = fee; + _bridge.allowed = true; + bridges[bridgeToken] = _bridge; + bridgeTokensList.push(bridgeToken); + emit BridgeTokenAdded(bridgeToken, limit, hourlyLimit, fee, paused); + } + + /// @notice Removes support for a token + /// @param bridgeToken Address of the bridge token to remove support for + function removeBridgeToken(address bridgeToken) external onlyGovernor { + if (IERC20(bridgeToken).balanceOf(address(this)) != 0) revert AssetStillControlledInReserves(); + delete bridges[bridgeToken]; + // Deletion from `bridgeTokensList` loop + uint256 bridgeTokensListLength = bridgeTokensList.length; + for (uint256 i; i < bridgeTokensListLength - 1; ++i) { + if (bridgeTokensList[i] == bridgeToken) { + // Replace the `bridgeToken` to remove with the last of the list + bridgeTokensList[i] = bridgeTokensList[bridgeTokensListLength - 1]; + break; + } + } + // Remove last element in array + bridgeTokensList.pop(); + emit BridgeTokenRemoved(bridgeToken); + } + + /// @notice Recovers any ERC20 token + /// @dev Can be used to withdraw bridge tokens for them to be de-bridged on mainnet + function recoverERC20(address tokenAddress, address to, uint256 amountToRecover) external onlyGovernor { + IERC20(tokenAddress).safeTransfer(to, amountToRecover); + emit Recovered(tokenAddress, to, amountToRecover); + } + + /// @notice Updates the `limit` amount for `bridgeToken` + function setLimit(address bridgeToken, uint256 limit) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bridges[bridgeToken].limit = limit; + emit BridgeTokenLimitUpdated(bridgeToken, limit); + } + + /// @notice Updates the `hourlyLimit` amount for `bridgeToken` + function setHourlyLimit(address bridgeToken, uint256 hourlyLimit) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bridges[bridgeToken].hourlyLimit = hourlyLimit; + emit BridgeTokenHourlyLimitUpdated(bridgeToken, hourlyLimit); + } + + /// @notice Updates the `chainTotalHourlyLimit` amount + function setChainTotalHourlyLimit(uint256 hourlyLimit) external onlyGovernorOrGuardian { + chainTotalHourlyLimit = hourlyLimit; + emit HourlyLimitUpdated(hourlyLimit); + } + + /// @notice Updates the `fee` value for `bridgeToken` + function setSwapFee(address bridgeToken, uint64 fee) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + if (fee > BASE_PARAMS) revert TooHighParameterValue(); + bridges[bridgeToken].fee = fee; + emit BridgeTokenFeeUpdated(bridgeToken, fee); + } + + /// @notice Pauses or unpauses swapping in and out for a token + function toggleBridge(address bridgeToken) external onlyGovernorOrGuardian { + if (!bridges[bridgeToken].allowed) revert InvalidToken(); + bool pausedStatus = bridges[bridgeToken].paused; + bridges[bridgeToken].paused = !pausedStatus; + emit BridgeTokenToggled(bridgeToken, !pausedStatus); + } + + /// @notice Toggles fees for the address `theAddress` + function toggleFeesForAddress(address theAddress) external onlyGovernorOrGuardian { + uint256 feeExemptStatus = 1 - isFeeExempt[theAddress]; + isFeeExempt[theAddress] = feeExemptStatus; + emit FeeToggled(theAddress, feeExemptStatus); + } +} diff --git a/contracts/agToken/polygon/utils/ERC20UpgradeableCustom.sol b/contracts/agToken/polygon/utils/ERC20UpgradeableCustom.sol new file mode 100644 index 0000000..43515d6 --- /dev/null +++ b/contracts/agToken/polygon/utils/ERC20UpgradeableCustom.sol @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/ERC20.sol) + +// solhint-disable custom-errors + +pragma solidity ^0.8.0; + +import "oz-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "oz-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import "oz-upgradeable/utils/ContextUpgradeable.sol"; +import "oz-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @dev Implementation of the {IERC20} interface modified by Angle Labs, Inc. + * + * This implementation has a custom burn function to avoid having a {Transfer} event to the zero address + * in some specific burn cases to avoid having Polygon PoS bridge catching this event + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20UpgradeableCustom is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + //solhint-disable-next-line + function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { + __Context_init_unchained(); + __ERC20_init_unchained(name_, symbol_); + } + + //solhint-disable-next-line + function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for `sender`'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + + uint256 currentAllowance = _allowances[sender][_msgSender()]; + require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance"); + unchecked { + _approve(sender, _msgSender(), currentAllowance - amount); + } + + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + uint256 currentAllowance = _allowances[_msgSender()][spender]; + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(_msgSender(), spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `sender` to `recipient`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + uint256 senderBalance = _balances[sender]; + require(senderBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[sender] = senderBalance - amount; + } + _balances[recipient] += amount; + + emit Transfer(sender, recipient, amount); + + _afterTokenTransfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Contrary to the other `burn` function, the {Transfer} event is not to the zero address + * but rather to this address: the reason is that not all burn events should be caught up + * by the PoS bridge + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burnCustom(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(this), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of `from`'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of `from`'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of `from`'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of `from`'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} + + uint256[45] private __gap; +} diff --git a/contracts/coreBorrow/CoreBorrow.sol b/contracts/coreBorrow/CoreBorrow.sol new file mode 100644 index 0000000..5d77c05 --- /dev/null +++ b/contracts/coreBorrow/CoreBorrow.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + * █ + ***** ▓▓▓ + * ▓▓▓▓▓▓▓ + * ///. ▓▓▓▓▓▓▓▓▓▓▓▓▓ + ***** //////// ▓▓▓▓▓▓▓ + * ///////////// ▓▓▓ + ▓▓ ////////////////// █ ▓▓ + ▓▓ ▓▓ /////////////////////// ▓▓ ▓▓ + ▓▓ ▓▓ //////////////////////////// ▓▓ ▓▓ + ▓▓ ▓▓ /////////▓▓▓///////▓▓▓///////// ▓▓ ▓▓ + ▓▓ ,////////////////////////////////////// ▓▓ ▓▓ + ▓▓ ////////////////////////////////////////// ▓▓ + ▓▓ //////////////////////▓▓▓▓///////////////////// + ,//////////////////////////////////////////////////// + .////////////////////////////////////////////////////////// + .//////////////////////////██.,//////////////////////////█ + .//////////////////////████..,./////////////////////██ + ...////////////////███████.....,.////////////////███ + ,.,////////////████████ ........,///////////████ + .,.,//////█████████ ,.......///////████ + ,..//████████ ........./████ + ..,██████ .....,███ + .██ ,.,█ + + + + ▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓ ▓▓▓▓ + ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓▓▓▓ + ▓▓▓ ▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ +*/ + +pragma solidity ^0.8.12; + +import "oz-upgradeable/access/AccessControlEnumerableUpgradeable.sol"; +import "oz-upgradeable/proxy/utils/Initializable.sol"; + +import "../interfaces/ICoreBorrow.sol"; +import "../interfaces/IFlashAngle.sol"; +import "../interfaces/ITreasury.sol"; + +/// @title CoreBorrow +/// @author Angle Labs, Inc. +/// @notice Core contract of the borrowing module. This contract handles the access control across all contracts +/// (it is read by all treasury contracts), and manages the `flashLoanModule`. It has no minting rights over the +/// stablecoin contracts +contract CoreBorrow is ICoreBorrow, Initializable, AccessControlEnumerableUpgradeable { + /// @notice Role for guardians + bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); + /// @notice Role for governors + bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); + /// @notice Role for treasury contract + bytes32 public constant FLASHLOANER_TREASURY_ROLE = keccak256("FLASHLOANER_TREASURY_ROLE"); + + // ============================= Reference ===================================== + + /// @notice Reference to the `flashLoanModule` with minting rights over the different stablecoins of the protocol + address public flashLoanModule; + + // =============================== Events ====================================== + + event FlashLoanModuleUpdated(address indexed _flashloanModule); + event CoreUpdated(address indexed _core); + + // =============================== Errors ====================================== + + error InvalidCore(); + error IncompatibleGovernorAndGuardian(); + error NotEnoughGovernorsLeft(); + error ZeroAddress(); + + /// @notice Initializes the `CoreBorrow` contract and the access control of the borrowing module + /// @param governor Address of the governor of the Angle Protocol + /// @param guardian Guardian address of the protocol + function initialize(address governor, address guardian) public initializer { + if (governor == address(0) || guardian == address(0)) revert ZeroAddress(); + if (governor == guardian) revert IncompatibleGovernorAndGuardian(); + _setupRole(GOVERNOR_ROLE, governor); + _setupRole(GUARDIAN_ROLE, guardian); + _setupRole(GUARDIAN_ROLE, governor); + _setRoleAdmin(GUARDIAN_ROLE, GOVERNOR_ROLE); + _setRoleAdmin(GOVERNOR_ROLE, GOVERNOR_ROLE); + _setRoleAdmin(FLASHLOANER_TREASURY_ROLE, GOVERNOR_ROLE); + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + // =========================== View Functions ================================== + + /// @inheritdoc ICoreBorrow + function isFlashLoanerTreasury(address treasury) external view returns (bool) { + return hasRole(FLASHLOANER_TREASURY_ROLE, treasury); + } + + /// @inheritdoc ICoreBorrow + function isGovernor(address admin) external view returns (bool) { + return hasRole(GOVERNOR_ROLE, admin); + } + + /// @inheritdoc ICoreBorrow + function isGovernorOrGuardian(address admin) external view returns (bool) { + return hasRole(GUARDIAN_ROLE, admin); + } + + // =========================== Governor Functions ============================== + + /// @notice Grants the `FLASHLOANER_TREASURY_ROLE` to a `treasury` contract + /// @param treasury Contract to grant the role to + /// @dev This function can be used to allow flash loans on a stablecoin of the protocol + function addFlashLoanerTreasuryRole(address treasury) external { + grantRole(FLASHLOANER_TREASURY_ROLE, treasury); + address _flashLoanModule = flashLoanModule; + if (_flashLoanModule != address(0)) { + // This call will revert if `treasury` is the zero address or if it is not linked + // to this `CoreBorrow` contract + ITreasury(treasury).setFlashLoanModule(_flashLoanModule); + IFlashAngle(_flashLoanModule).addStablecoinSupport(treasury); + } + } + + /// @notice Adds a governor in the protocol + /// @param governor Address to grant the role to + /// @dev It is necessary to call this function to grant a governor role to make sure + /// all governors also have the guardian role + function addGovernor(address governor) external { + grantRole(GOVERNOR_ROLE, governor); + grantRole(GUARDIAN_ROLE, governor); + } + + /// @notice Revokes the flash loan ability for a stablecoin + /// @param treasury Treasury address associated with the stablecoin for which flash loans + /// should no longer be available + function removeFlashLoanerTreasuryRole(address treasury) external { + revokeRole(FLASHLOANER_TREASURY_ROLE, treasury); + ITreasury(treasury).setFlashLoanModule(address(0)); + address _flashLoanModule = flashLoanModule; + if (_flashLoanModule != address(0)) { + IFlashAngle(flashLoanModule).removeStablecoinSupport(treasury); + } + } + + /// @notice Revokes a governor from the protocol + /// @param governor Address to remove the role to + /// @dev It is necessary to call this function to remove a governor role to make sure + /// the address also loses its guardian role + function removeGovernor(address governor) external { + if (getRoleMemberCount(GOVERNOR_ROLE) <= 1) revert NotEnoughGovernorsLeft(); + revokeRole(GUARDIAN_ROLE, governor); + revokeRole(GOVERNOR_ROLE, governor); + } + + /// @notice Changes the `flashLoanModule` of the protocol + /// @param _flashLoanModule Address of the new flash loan module + function setFlashLoanModule(address _flashLoanModule) external onlyRole(GOVERNOR_ROLE) { + if (_flashLoanModule != address(0)) { + if (address(IFlashAngle(_flashLoanModule).core()) != address(this)) revert InvalidCore(); + } + uint256 count = getRoleMemberCount(FLASHLOANER_TREASURY_ROLE); + for (uint256 i; i < count; ++i) { + ITreasury(getRoleMember(FLASHLOANER_TREASURY_ROLE, i)).setFlashLoanModule(_flashLoanModule); + } + flashLoanModule = _flashLoanModule; + emit FlashLoanModuleUpdated(_flashLoanModule); + } + + /// @notice Changes the core contract of the protocol + /// @param _core New core contract + /// @dev This function verifies that all governors of the current core contract are also governors + /// of the new core contract. It also notifies the `flashLoanModule` of the change. + /// @dev Governance wishing to change the core contract should also make sure to call `setCore` + /// in the different treasury contracts + function setCore(ICoreBorrow _core) external onlyRole(GOVERNOR_ROLE) { + uint256 count = getRoleMemberCount(GOVERNOR_ROLE); + bool success; + for (uint256 i; i < count; ++i) { + success = _core.isGovernor(getRoleMember(GOVERNOR_ROLE, i)); + if (!success) break; + } + if (!success) revert InvalidCore(); + address _flashLoanModule = flashLoanModule; + if (_flashLoanModule != address(0)) IFlashAngle(_flashLoanModule).setCore(address(_core)); + emit CoreUpdated(address(_core)); + } +} diff --git a/contracts/external/ProxyAdmin.sol b/contracts/external/ProxyAdmin.sol new file mode 100644 index 0000000..29abc23 --- /dev/null +++ b/contracts/external/ProxyAdmin.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "./TransparentUpgradeableProxy.sol"; +import "oz/access/Ownable.sol"; + +/** + * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an + * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. + * This contract was fully forked from OpenZeppelin `ProxyAdmin` + */ +contract ProxyAdmin is Ownable { + /** + * @dev Returns the current implementation of `proxy`. + * + * Requirements: + * + * - This contract must be the admin of `proxy`. + */ + function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) { + // We need to manually run the static call since the getter cannot be flagged as view + // bytes4(keccak256("implementation()")) == 0x5c60da1b + (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b"); + require(success); + return abi.decode(returndata, (address)); + } + + /** + * @dev Returns the current admin of `proxy`. + * + * Requirements: + * + * - This contract must be the admin of `proxy`. + */ + function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) { + // We need to manually run the static call since the getter cannot be flagged as view + // bytes4(keccak256("admin()")) == 0xf851a440 + (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440"); + require(success); + return abi.decode(returndata, (address)); + } + + /** + * @dev Changes the admin of `proxy` to `newAdmin`. + * + * Requirements: + * + * - This contract must be the current admin of `proxy`. + */ + function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner { + proxy.changeAdmin(newAdmin); + } + + /** + * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}. + * + * Requirements: + * + * - This contract must be the admin of `proxy`. + */ + function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner { + proxy.upgradeTo(implementation); + } + + /** + * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See + * {TransparentUpgradeableProxy-upgradeToAndCall}. + * + * Requirements: + * + * - This contract must be the admin of `proxy`. + */ + function upgradeAndCall( + TransparentUpgradeableProxy proxy, + address implementation, + bytes memory data + ) public payable virtual onlyOwner { + proxy.upgradeToAndCall{ value: msg.value }(implementation, data); + } +} diff --git a/contracts/external/TransparentUpgradeableProxy.sol b/contracts/external/TransparentUpgradeableProxy.sol new file mode 100644 index 0000000..b3490d9 --- /dev/null +++ b/contracts/external/TransparentUpgradeableProxy.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "oz/proxy/ERC1967/ERC1967Proxy.sol"; + +/** + * @dev This contract implements a proxy that is upgradeable by an admin. It is fully forked from OpenZeppelin + * `TransparentUpgradeableProxy` + * + * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector + * clashing], which can potentially be used in an attack, this contract uses the + * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two + * things that go hand in hand: + * + * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if + * that call matches one of the admin functions exposed by the proxy itself. + * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the + * implementation. If the admin tries to call a function on the implementation it will fail with an error that says + * "admin cannot fallback to proxy target". + * + * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing + * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due + * to sudden errors when trying to call a function from the proxy implementation. + * + * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, + * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. + */ +contract TransparentUpgradeableProxy is ERC1967Proxy { + /** + * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and + * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. + */ + constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { + assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); + _changeAdmin(admin_); + } + + /** + * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. + */ + modifier ifAdmin() { + if (msg.sender == _getAdmin()) { + _; + } else { + _fallback(); + } + } + + /** + * @dev Returns the current admin. + * + * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the + * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` + */ + function admin() external ifAdmin returns (address admin_) { + admin_ = _getAdmin(); + } + + /** + * @dev Returns the current implementation. + * + * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the + * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` + */ + function implementation() external ifAdmin returns (address implementation_) { + implementation_ = _implementation(); + } + + /** + * @dev Changes the admin of the proxy. + * + * Emits an {AdminChanged} event. + * + * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}. + */ + function changeAdmin(address newAdmin) external virtual ifAdmin { + _changeAdmin(newAdmin); + } + + /** + * @dev Upgrade the implementation of the proxy. + * + * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}. + */ + function upgradeTo(address newImplementation) external ifAdmin { + _upgradeToAndCall(newImplementation, bytes(""), false); + } + + /** + * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified + * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the + * proxied contract. + * + * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}. + */ + function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin { + _upgradeToAndCall(newImplementation, data, true); + } + + /** + * @dev Returns the current admin. + */ + function _admin() internal view virtual returns (address) { + return _getAdmin(); + } + + /** + * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}. + */ + function _beforeFallback() internal virtual override { + require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target"); + super._beforeFallback(); + } +} diff --git a/contracts/flashloan/FlashAngle.sol b/contracts/flashloan/FlashAngle.sol new file mode 100644 index 0000000..7b966db --- /dev/null +++ b/contracts/flashloan/FlashAngle.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "oz-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "oz-upgradeable/proxy/utils/Initializable.sol"; +import "oz/interfaces/IERC3156FlashBorrower.sol"; +import "oz/interfaces/IERC3156FlashLender.sol"; +import "oz/token/ERC20/IERC20.sol"; +import "oz/token/ERC20/utils/SafeERC20.sol"; + +import "../interfaces/IAgToken.sol"; +import "../interfaces/ICoreBorrow.sol"; +import "../interfaces/IFlashAngle.sol"; +import "../interfaces/ITreasury.sol"; + +/// @title FlashAngle +/// @author Angle Labs, Inc. +/// @notice Contract to take flash loans on top of several AgToken contracts +contract FlashAngle is IERC3156FlashLender, IFlashAngle, Initializable, ReentrancyGuardUpgradeable { + using SafeERC20 for IERC20; + /// @notice Base used for parameter computation + uint256 public constant BASE_PARAMS = 10 ** 9; + /// @notice Success message received when calling a `FlashBorrower` contract + bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); + + /// @notice Struct encoding for a given stablecoin the parameters + struct StablecoinData { + // Maximum amount borrowable for this stablecoin + uint256 maxBorrowable; + // Flash loan fee taken by the protocol for a flash loan on this stablecoin + uint64 flashLoanFee; + // Treasury address responsible of the stablecoin + address treasury; + } + + // ======================= Parameters and References =========================== + + /// @notice Maps a stablecoin to the data and parameters for flash loans + mapping(IAgToken => StablecoinData) public stablecoinMap; + /// @inheritdoc IFlashAngle + ICoreBorrow public core; + + // =============================== Event ======================================= + + event FlashLoan(address indexed stablecoin, uint256 amount, IERC3156FlashBorrower indexed receiver); + event FlashLoanParametersUpdated(IAgToken indexed stablecoin, uint64 _flashLoanFee, uint256 _maxBorrowable); + + // =============================== Errors ====================================== + + error InvalidReturnMessage(); + error NotCore(); + error NotGovernorOrGuardian(); + error NotTreasury(); + error TooBigAmount(); + error TooHighParameterValue(); + error UnsupportedStablecoin(); + error ZeroAddress(); + + /// @notice Initializes the contract + /// @param _core Core address handling this module + function initialize(ICoreBorrow _core) public initializer { + if (address(_core) == address(0)) revert ZeroAddress(); + core = _core; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + // =================================== Modifiers =============================== + + /// @notice Checks whether the sender is the core contract + modifier onlyCore() { + if (msg.sender != address(core)) revert NotCore(); + _; + } + + /// @notice Checks whether a given stablecoin has been initialized in this contract + /// @param stablecoin Stablecoin to check + /// @dev To check whether a stablecoin has been initialized, we just need to check whether its associated + /// `treasury` address is not null in the `stablecoinMap`. This is what's checked in the `CoreBorrow` contract + /// when adding support for a stablecoin + modifier onlyExistingStablecoin(IAgToken stablecoin) { + if (stablecoinMap[stablecoin].treasury == address(0)) revert UnsupportedStablecoin(); + _; + } + + // ================================ ERC3156 Spec =============================== + + /// @inheritdoc IERC3156FlashLender + function flashFee(address token, uint256 amount) external view returns (uint256) { + return _flashFee(token, amount); + } + + /// @inheritdoc IERC3156FlashLender + function maxFlashLoan(address token) external view returns (uint256) { + // It will be 0 anyway if the token was not added + return stablecoinMap[IAgToken(token)].maxBorrowable; + } + + /// @inheritdoc IERC3156FlashLender + function flashLoan( + IERC3156FlashBorrower receiver, + address token, + uint256 amount, + bytes calldata data + ) external nonReentrant returns (bool) { + uint256 fee = _flashFee(token, amount); + if (amount > stablecoinMap[IAgToken(token)].maxBorrowable) revert TooBigAmount(); + IAgToken(token).mint(address(receiver), amount); + if (receiver.onFlashLoan(msg.sender, token, amount, fee, data) != CALLBACK_SUCCESS) + revert InvalidReturnMessage(); + // Token must be an agToken here so normally no need to use `safeTransferFrom`, but out of safety + // and in case governance whitelists an agToken which does not have a correct implementation, we prefer + // to use `safeTransferFrom` here + IERC20(token).safeTransferFrom(address(receiver), address(this), amount + fee); + IAgToken(token).burnSelf(amount, address(this)); + emit FlashLoan(token, amount, receiver); + return true; + } + + /// @notice Internal function to compute the fee induced for taking a flash loan of `amount` of `token` + /// @param token The loan currency + /// @param amount The amount of tokens lent + /// @dev This function will revert if the `token` requested is not whitelisted here + function _flashFee( + address token, + uint256 amount + ) internal view onlyExistingStablecoin(IAgToken(token)) returns (uint256) { + return (amount * stablecoinMap[IAgToken(token)].flashLoanFee) / BASE_PARAMS; + } + + // ============================ Treasury Only Function ========================= + + /// @inheritdoc IFlashAngle + function accrueInterestToTreasury(IAgToken stablecoin) external returns (uint256 balance) { + address treasury = stablecoinMap[stablecoin].treasury; + if (msg.sender != treasury) revert NotTreasury(); + balance = stablecoin.balanceOf(address(this)); + IERC20(address(stablecoin)).safeTransfer(treasury, balance); + } + + // =========================== Governance Only Function ======================== + + /// @notice Sets the parameters for a given stablecoin + /// @param stablecoin Stablecoin to change the parameters for + /// @param _flashLoanFee New flash loan fee for this stablecoin + /// @param _maxBorrowable Maximum amount that can be borrowed in a single flash loan + /// @dev Setting a `maxBorrowable` parameter equal to 0 is a way to pause the functionality + /// @dev Parameters can only be modified for whitelisted stablecoins + function setFlashLoanParameters( + IAgToken stablecoin, + uint64 _flashLoanFee, + uint256 _maxBorrowable + ) external onlyExistingStablecoin(stablecoin) { + if (!core.isGovernorOrGuardian(msg.sender)) revert NotGovernorOrGuardian(); + if (_flashLoanFee > BASE_PARAMS) revert TooHighParameterValue(); + stablecoinMap[stablecoin].flashLoanFee = _flashLoanFee; + stablecoinMap[stablecoin].maxBorrowable = _maxBorrowable; + emit FlashLoanParametersUpdated(stablecoin, _flashLoanFee, _maxBorrowable); + } + + // =========================== CoreBorrow Only Functions ======================= + + /// @inheritdoc IFlashAngle + function addStablecoinSupport(address _treasury) external onlyCore { + stablecoinMap[IAgToken(ITreasury(_treasury).stablecoin())].treasury = _treasury; + } + + /// @inheritdoc IFlashAngle + function removeStablecoinSupport(address _treasury) external onlyCore { + delete stablecoinMap[IAgToken(ITreasury(_treasury).stablecoin())]; + } + + /// @inheritdoc IFlashAngle + function setCore(address _core) external onlyCore { + core = ICoreBorrow(_core); + } +} diff --git a/contracts/interfaces/IAgToken.sol b/contracts/interfaces/IAgToken.sol new file mode 100644 index 0000000..dae0d05 --- /dev/null +++ b/contracts/interfaces/IAgToken.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "oz-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +/// @title IAgToken +/// @author Angle Labs, Inc. +/// @notice Interface for the stablecoins `AgToken` contracts +/// @dev This interface only contains functions of the `AgToken` contract which are called by other contracts +/// of this module or of the first module of the Angle Protocol +interface IAgToken is IERC20Upgradeable { + // ======================= Minter Role Only Functions =========================== + + /// @notice Lets the `StableMaster` contract or another whitelisted contract mint agTokens + /// @param account Address to mint to + /// @param amount Amount to mint + /// @dev The contracts allowed to issue agTokens are the `StableMaster` contract, `VaultManager` contracts + /// associated to this stablecoin as well as the flash loan module (if activated) and potentially contracts + /// whitelisted by governance + function mint(address account, uint256 amount) external; + + /// @notice Burns `amount` tokens from a `burner` address after being asked to by `sender` + /// @param amount Amount of tokens to burn + /// @param burner Address to burn from + /// @param sender Address which requested the burn from `burner` + /// @dev This method is to be called by a contract with the minter right after being requested + /// to do so by a `sender` address willing to burn tokens from another `burner` address + /// @dev The method checks the allowance between the `sender` and the `burner` + function burnFrom(uint256 amount, address burner, address sender) external; + + /// @notice Burns `amount` tokens from a `burner` address + /// @param amount Amount of tokens to burn + /// @param burner Address to burn from + /// @dev This method is to be called by a contract with a minter right on the AgToken after being + /// requested to do so by an address willing to burn tokens from its address + function burnSelf(uint256 amount, address burner) external; + + /// @notice Allows anyone to burn stablecoins + /// @param amount Amount of stablecoins to burn + /// @dev This function can typically be called if there is a settlement mechanism to burn stablecoins + function burnStablecoin(uint256 amount) external; + + // ========================= Treasury Only Functions =========================== + + /// @notice Adds a minter in the contract + /// @param minter Minter address to add + /// @dev Zero address checks are performed directly in the `Treasury` contract + function addMinter(address minter) external; + + /// @notice Removes a minter from the contract + /// @param minter Minter address to remove + /// @dev This function can also be called by a minter wishing to revoke itself + function removeMinter(address minter) external; + + /// @notice Sets a new treasury contract + /// @param _treasury New treasury address + function setTreasury(address _treasury) external; + + // ========================= External functions ================================ + + /// @notice Checks whether an address has the right to mint agTokens + /// @param minter Address for which the minting right should be checked + /// @return Whether the address has the right to mint agTokens or not + function isMinter(address minter) external view returns (bool); + + /// @notice Get the associated treasury + function treasury() external view returns (address); +} diff --git a/contracts/interfaces/IAgTokenSideChainMultiBridge.sol b/contracts/interfaces/IAgTokenSideChainMultiBridge.sol new file mode 100644 index 0000000..2bc9115 --- /dev/null +++ b/contracts/interfaces/IAgTokenSideChainMultiBridge.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "oz-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol"; +import "oz-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +/// @title IAgTokenSideChainMultiBridge +/// @author Angle Labs, Inc. +/// @notice Interface for the canonical `AgToken` contracts +/// @dev This interface only contains functions useful for bridge tokens to interact with the canonical token +interface IAgTokenSideChainMultiBridge is IERC20PermitUpgradeable, IERC20Upgradeable { + /// @notice Mints the canonical token from a supported bridge token + /// @param bridgeToken Bridge token to use to mint + /// @param amount Amount of bridge tokens to send + /// @param to Address to which the stablecoin should be sent + /// @return Amount of the canonical stablecoin actually minted + /// @dev Some fees may be taken by the protocol depending on the token used and on the address calling + function swapIn(address bridgeToken, uint256 amount, address to) external returns (uint256); + + /// @notice Burns the canonical token in exchange for a bridge token + /// @param bridgeToken Bridge token required + /// @param amount Amount of canonical tokens to burn + /// @param to Address to which the bridge token should be sent + /// @return Amount of bridge tokens actually sent back + /// @dev Some fees may be taken by the protocol depending on the token used and on the address calling + function swapOut(address bridgeToken, uint256 amount, address to) external returns (uint256); +} diff --git a/contracts/interfaces/ICoreBorrow.sol b/contracts/interfaces/ICoreBorrow.sol new file mode 100644 index 0000000..fabca63 --- /dev/null +++ b/contracts/interfaces/ICoreBorrow.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +/// @title ICoreBorrow +/// @author Angle Labs, Inc. +/// @notice Interface for the `CoreBorrow` contract +/// @dev This interface only contains functions of the `CoreBorrow` contract which are called by other contracts +/// of this module +interface ICoreBorrow { + /// @notice Checks if an address corresponds to a treasury of a stablecoin with a flash loan + /// module initialized on it + /// @param treasury Address to check + /// @return Whether the address has the `FLASHLOANER_TREASURY_ROLE` or not + function isFlashLoanerTreasury(address treasury) external view returns (bool); + + /// @notice Checks whether an address is governor of the Angle Protocol or not + /// @param admin Address to check + /// @return Whether the address has the `GOVERNOR_ROLE` or not + function isGovernor(address admin) external view returns (bool); + + /// @notice Checks whether an address is governor or a guardian of the Angle Protocol or not + /// @param admin Address to check + /// @return Whether the address has the `GUARDIAN_ROLE` or not + /// @dev Governance should make sure when adding a governor to also give this governor the guardian + /// role by calling the `addGovernor` function + function isGovernorOrGuardian(address admin) external view returns (bool); +} diff --git a/contracts/interfaces/IFlashAngle.sol b/contracts/interfaces/IFlashAngle.sol new file mode 100644 index 0000000..e97715d --- /dev/null +++ b/contracts/interfaces/IFlashAngle.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "./IAgToken.sol"; +import "./ICoreBorrow.sol"; + +/// @title IFlashAngle +/// @author Angle Labs, Inc. +/// @notice Interface for the `FlashAngle` contract +/// @dev This interface only contains functions of the contract which are called by other contracts +/// of this module +interface IFlashAngle { + /// @notice Reference to the `CoreBorrow` contract managing the FlashLoan module + function core() external view returns (ICoreBorrow); + + /// @notice Sends the fees taken from flash loans to the treasury contract associated to the stablecoin + /// @param stablecoin Stablecoin from which profits should be sent + /// @return balance Amount of profits sent + /// @dev This function can only be called by the treasury contract + function accrueInterestToTreasury(IAgToken stablecoin) external returns (uint256 balance); + + /// @notice Adds support for a stablecoin + /// @param _treasury Treasury associated to the stablecoin to add support for + /// @dev This function can only be called by the `CoreBorrow` contract + function addStablecoinSupport(address _treasury) external; + + /// @notice Removes support for a stablecoin + /// @param _treasury Treasury associated to the stablecoin to remove support for + /// @dev This function can only be called by the `CoreBorrow` contract + function removeStablecoinSupport(address _treasury) external; + + /// @notice Sets a new core contract + /// @param _core Core contract address to set + /// @dev This function can only be called by the `CoreBorrow` contract + function setCore(address _core) external; +} diff --git a/contracts/interfaces/IOracle.sol b/contracts/interfaces/IOracle.sol new file mode 100644 index 0000000..4f23c53 --- /dev/null +++ b/contracts/interfaces/IOracle.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "./ITreasury.sol"; +import "chainlink/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +/// @title IOracle +/// @author Angle Labs, Inc. +/// @notice Interface for the `Oracle` contract +/// @dev This interface only contains functions of the contract which are called by other contracts +/// of this module +interface IOracle { + /// @notice Reads the rate from the Chainlink circuit and other data provided + /// @return quoteAmount The current rate between the in-currency and out-currency in the base + /// of the out currency + /// @dev For instance if the out currency is EUR (and hence agEUR), then the base of the returned + /// value is 10**18 + function read() external view returns (uint256); + + /// @notice Changes the treasury contract + /// @param _treasury Address of the new treasury contract + /// @dev This function can be called by an approved `VaultManager` contract which can call + /// this function after being requested to do so by a `treasury` contract + /// @dev In some situations (like reactor contracts), the `VaultManager` may not directly be linked + /// to the `oracle` contract and as such we may need governors to be able to call this function as well + function setTreasury(address _treasury) external; + + /// @notice Reference to the `treasury` contract handling this `VaultManager` + function treasury() external view returns (ITreasury treasury); + + /// @notice Array with the list of Chainlink feeds in the order in which they are read + function circuitChainlink() external view returns (AggregatorV3Interface[] memory); +} diff --git a/contracts/interfaces/ITreasury.sol b/contracts/interfaces/ITreasury.sol new file mode 100644 index 0000000..5cdfca6 --- /dev/null +++ b/contracts/interfaces/ITreasury.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "./IAgToken.sol"; +import "./ICoreBorrow.sol"; +import "./IFlashAngle.sol"; + +/// @title ITreasury +/// @author Angle Labs, Inc. +/// @notice Interface for the `Treasury` contract +/// @dev This interface only contains functions of the `Treasury` which are called by other contracts +/// of this module +interface ITreasury { + /// @notice Stablecoin handled by this `treasury` contract + function stablecoin() external view returns (IAgToken); + + /// @notice Checks whether a given address has the governor role + /// @param admin Address to check + /// @return Whether the address has the governor role + /// @dev Access control is only kept in the `CoreBorrow` contract + function isGovernor(address admin) external view returns (bool); + + /// @notice Checks whether a given address has the guardian or the governor role + /// @param admin Address to check + /// @return Whether the address has the guardian or the governor role + /// @dev Access control is only kept in the `CoreBorrow` contract which means that this function + /// queries the `CoreBorrow` contract + function isGovernorOrGuardian(address admin) external view returns (bool); + + /// @notice Checks whether a given address has well been initialized in this contract + /// as a `VaultManager` + /// @param _vaultManager Address to check + /// @return Whether the address has been initialized or not + function isVaultManager(address _vaultManager) external view returns (bool); + + /// @notice Sets a new flash loan module for this stablecoin + /// @param _flashLoanModule Reference to the new flash loan module + /// @dev This function removes the minting right to the old flash loan module and grants + /// it to the new module + function setFlashLoanModule(address _flashLoanModule) external; + + /// @notice Gets the vault manager list + function vaultManagerList(uint256 i) external returns (address); +} diff --git a/contracts/interfaces/IVaultManager.sol b/contracts/interfaces/IVaultManager.sol new file mode 100644 index 0000000..fa23e55 --- /dev/null +++ b/contracts/interfaces/IVaultManager.sol @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "oz/interfaces/IERC721Metadata.sol"; +import "oz/token/ERC20/IERC20.sol"; +import "./ITreasury.sol"; +import "./IOracle.sol"; + +// ========================= Key Structs and Enums ============================= + +/// @notice Parameters associated to a given `VaultManager` contract: these all correspond +/// to parameters which signification is detailed in the `VaultManagerStorage` file +struct VaultParameters { + uint256 debtCeiling; + uint64 collateralFactor; + uint64 targetHealthFactor; + uint64 interestRate; + uint64 liquidationSurcharge; + uint64 maxLiquidationDiscount; + bool whitelistingActivated; + uint256 baseBoost; +} + +/// @notice Data stored to track someone's loan (or equivalently called position) +struct Vault { + // Amount of collateral deposited in the vault, in collateral decimals. For example, if the collateral + // is USDC with 6 decimals, then `collateralAmount` will be in base 10**6 + uint256 collateralAmount; + // Normalized value of the debt (that is to say of the stablecoins borrowed). It is expressed + // in the base of Angle stablecoins (i.e. `BASE_TOKENS = 10**18`) + uint256 normalizedDebt; +} + +/// @notice For a given `vaultID`, this encodes a liquidation opportunity that is to say details about the maximum +/// amount that could be repaid by liquidating the position +/// @dev All the values are null in the case of a vault which cannot be liquidated under these conditions +struct LiquidationOpportunity { + // Maximum stablecoin amount that can be repaid upon liquidating the vault + uint256 maxStablecoinAmountToRepay; + // Collateral amount given to the person in the case where the maximum amount to repay is given + uint256 maxCollateralAmountGiven; + // Threshold value of stablecoin amount to repay: it is ok for a liquidator to repay below threshold, + // but if this threshold is non null and the liquidator wants to repay more than threshold, it should repay + // the max stablecoin amount given in this vault + uint256 thresholdRepayAmount; + // Discount proposed to the liquidator on the collateral + uint256 discount; + // Amount of debt in the vault + uint256 currentDebt; +} + +/// @notice Data stored during a liquidation process to keep in memory what's due to a liquidator and some +/// essential data for vaults being liquidated +struct LiquidatorData { + // Current amount of stablecoins the liquidator should give to the contract + uint256 stablecoinAmountToReceive; + // Current amount of collateral the contract should give to the liquidator + uint256 collateralAmountToGive; + // Bad debt accrued across the liquidation process + uint256 badDebtFromLiquidation; + // Oracle value (in stablecoin base) at the time of the liquidation + uint256 oracleValue; + // Value of the `interestAccumulator` at the time of the call + uint256 newInterestAccumulator; +} + +/// @notice Data to track during a series of action the amount to give or receive in stablecoins and collateral +/// to the caller or associated addresses +struct PaymentData { + // Stablecoin amount the contract should give + uint256 stablecoinAmountToGive; + // Stablecoin amount owed to the contract + uint256 stablecoinAmountToReceive; + // Collateral amount the contract should give + uint256 collateralAmountToGive; + // Collateral amount owed to the contract + uint256 collateralAmountToReceive; +} + +/// @notice Actions possible when composing calls to the different entry functions proposed +enum ActionType { + createVault, + closeVault, + addCollateral, + removeCollateral, + repayDebt, + borrow, + getDebtIn, + permit +} + +// ========================= Interfaces ============================= + +/// @title IVaultManagerFunctions +/// @author Angle Labs, Inc. +/// @notice Interface for the `VaultManager` contract +/// @dev This interface only contains functions of the contract which are called by other contracts +/// of this module (without getters) +interface IVaultManagerFunctions { + /// @notice Accrues interest accumulated across all vaults to the surplus and sends the surplus to the treasury + /// @return surplusValue Value of the surplus communicated to the `Treasury` + /// @return badDebtValue Value of the bad debt communicated to the `Treasury` + /// @dev `surplus` and `badDebt` should be reset to 0 once their current value have been given to the + /// `treasury` contract + function accrueInterestToTreasury() external returns (uint256 surplusValue, uint256 badDebtValue); + + /// @notice Removes debt from a vault after being requested to do so by another `VaultManager` contract + /// @param vaultID ID of the vault to remove debt from + /// @param amountStablecoins Amount of stablecoins to remove from the debt: this amount is to be converted to an + /// internal debt amount + /// @param senderBorrowFee Borrowing fees from the contract which requested this: this is to make sure that people + /// are not arbitraging difference in minting fees + /// @param senderRepayFee Repay fees from the contract which requested this: this is to make sure that people + /// are not arbitraging differences in repay fees + /// @dev This function can only be called from a vaultManager registered in the same Treasury + function getDebtOut( + uint256 vaultID, + uint256 amountStablecoins, + uint256 senderBorrowFee, + uint256 senderRepayFee + ) external; + + /// @notice Gets the current debt of a vault + /// @param vaultID ID of the vault to check + /// @return Debt of the vault + function getVaultDebt(uint256 vaultID) external view returns (uint256); + + /// @notice Gets the total debt across all vaults + /// @return Total debt across all vaults, taking into account the interest accumulated + /// over time + function getTotalDebt() external view returns (uint256); + + /// @notice Sets the treasury contract + /// @param _treasury New treasury contract + /// @dev All required checks when setting up a treasury contract are performed in the contract + /// calling this function + function setTreasury(address _treasury) external; + + /// @notice Creates a vault + /// @param toVault Address for which the va + /// @return vaultID ID of the vault created + /// @dev This function just creates the vault without doing any collateral or + function createVault(address toVault) external returns (uint256); + + /// @notice Allows composability between calls to the different entry points of this module. Any user calling + /// this function can perform any of the allowed actions in the order of their choice + /// @param actions Set of actions to perform + /// @param datas Data to be decoded for each action: it can include like the `vaultID` or the `stablecoinAmount` + /// to borrow + /// @param from Address from which stablecoins will be taken if one action includes burning stablecoins. + /// This address should either be the `msg.sender` or be approved by the latter + /// @param to Address to which stablecoins and/or collateral will be sent in case of + /// @param who Address of the contract to handle in case of repayment of stablecoins from received collateral + /// @param repayData Data to pass to the repayment contract in case of + /// @return paymentData Struct containing the accounting changes from the protocol's perspective (like how much + /// of collateral or how much has been received). Note that the values in the struct are not aggregated and you + /// could have in the output a positive amount of stablecoins to receive as well as a positive amount of + /// stablecoins to give + /// @dev This function is optimized to reduce gas cost due to payment from or to the user and that expensive calls + /// or computations (like `oracleValue`) are done only once + /// @dev When specifying `vaultID` in `data`, it is important to know that if you specify `vaultID = 0`, + /// it will simply use the latest `vaultID`. This is the default behavior, and unless you're engaging into some + /// complex protocol actions, it is encouraged to use `vaultID = 0` only when the first action of the + /// batch is `createVault` + function angle( + ActionType[] memory actions, + bytes[] memory datas, + address from, + address to, + address who, + bytes memory repayData + ) external returns (PaymentData memory paymentData); + + /// @notice This function is a wrapper built on top of the function above. It enables users to interact with + /// the contract without having to provide `who` and `repayData` parameters + function angle( + ActionType[] memory actions, + bytes[] memory datas, + address from, + address to + ) external returns (PaymentData memory paymentData); + + /// @notice Initializes the `VaultManager` contract + /// @param _treasury Treasury address handling the contract + /// @param _collateral Collateral supported by this contract + /// @param _oracle Oracle contract used + /// @param _symbol Symbol used to define the `VaultManager` name and symbol + /// @dev The parameters and the oracle are the only elements which could be modified once the + /// contract has been initialized + /// @dev For the contract to be fully initialized, governance needs to set the parameters for the liquidation + /// boost + function initialize( + ITreasury _treasury, + IERC20 _collateral, + IOracle _oracle, + VaultParameters calldata params, + string memory _symbol + ) external; + + /// @notice Minimum amount of debt a vault can have, expressed in `BASE_TOKENS` that is to say the base + /// of the agTokens + function dust() external view returns (uint256); + + /// @notice Pauses external permissionless functions of the contract + function togglePause() external; +} + +/// @title IVaultManagerStorage +/// @author Angle Labs, Inc. +/// @notice Interface for the `VaultManager` contract +/// @dev This interface contains getters of the contract's public variables used by other contracts +/// of this module +interface IVaultManagerStorage { + /// @notice Encodes the maximum ratio stablecoin/collateral a vault can have before being liquidated. It's what + /// determines the minimum collateral ratio of a position + function collateralFactor() external view returns (uint64); + + /// @notice Stablecoin handled by this contract. Another `VaultManager` contract could have + /// the same rights as this `VaultManager` on the stablecoin contract + function stablecoin() external view returns (IAgToken); + + /// @notice Reference to the `treasury` contract handling this `VaultManager` + function treasury() external view returns (ITreasury); + + /// @notice Oracle contract to get access to the price of the collateral with respect to the stablecoin + function oracle() external view returns (IOracle); + + /// @notice The `interestAccumulator` variable keeps track of the interest that should accrue to the protocol. + /// The stored value is not necessarily the true value: this one is recomputed every time an action takes place + /// within the protocol. It is in base `BASE_INTEREST` + function interestAccumulator() external view returns (uint256); + + /// @notice Reference to the collateral handled by this `VaultManager` + function collateral() external view returns (IERC20); + + /// @notice Total normalized amount of stablecoins borrowed, not taking into account the potential + /// bad debt accumulated. This value is expressed in the base of Angle stablecoins (`BASE_TOKENS = 10**18`) + function totalNormalizedDebt() external view returns (uint256); + + /// @notice Maximum amount of stablecoins that can be issued with this contract. It is expressed in `BASE_TOKENS` + function debtCeiling() external view returns (uint256); + + /// @notice Maps a `vaultID` to its data (namely collateral amount and normalized debt) + function vaultData(uint256 vaultID) external view returns (uint256 collateralAmount, uint256 normalizedDebt); + + /// @notice ID of the last vault created. The `vaultIDCount` variables serves as a counter to generate a unique + /// `vaultID` for each vault: it is like `tokenID` in basic ERC721 contracts + function vaultIDCount() external view returns (uint256); +} + +/// @title IVaultManager +/// @author Angle Labs, Inc. +/// @notice Interface for the `VaultManager` contract +interface IVaultManager is IVaultManagerFunctions, IVaultManagerStorage, IERC721Metadata { + function isApprovedOrOwner(address spender, uint256 vaultID) external view returns (bool); +} + +/// @title IVaultManagerListing +/// @author Angle Labs, Inc. +/// @notice Interface for the `VaultManagerListing` contract +interface IVaultManagerListing is IVaultManager { + /// @notice Get the collateral owned by `user` in the contract + /// @dev This function effectively sums the collateral amounts of all the vaults owned by `user` + function getUserCollateral(address user) external view returns (uint256); +} diff --git a/contracts/interfaces/coreModule/IOracleCore.sol b/contracts/interfaces/coreModule/IOracleCore.sol new file mode 100644 index 0000000..1785ba2 --- /dev/null +++ b/contracts/interfaces/coreModule/IOracleCore.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +/// @title IOracleCore +/// @author Angle Labs, Inc. +interface IOracleCore { + function readUpper() external view returns (uint256); + + function readQuoteLower(uint256 baseAmount) external view returns (uint256); +} diff --git a/contracts/interfaces/coreModule/IPerpetualManager.sol b/contracts/interfaces/coreModule/IPerpetualManager.sol new file mode 100644 index 0000000..d5be036 --- /dev/null +++ b/contracts/interfaces/coreModule/IPerpetualManager.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +/// @title IPerpetualManager +/// @author Angle Labs, Inc. +interface IPerpetualManager { + function totalHedgeAmount() external view returns (uint256); + + function maintenanceMargin() external view returns (uint64); + + function maxLeverage() external view returns (uint64); + + function targetHAHedge() external view returns (uint64); + + function limitHAHedge() external view returns (uint64); + + function lockTime() external view returns (uint64); + + function haBonusMalusDeposit() external view returns (uint64); + + function haBonusMalusWithdraw() external view returns (uint64); + + function xHAFeesDeposit(uint256) external view returns (uint64); + + function yHAFeesDeposit(uint256) external view returns (uint64); + + function xHAFeesWithdraw(uint256) external view returns (uint64); + + function yHAFeesWithdraw(uint256) external view returns (uint64); +} diff --git a/contracts/interfaces/coreModule/IStableMaster.sol b/contracts/interfaces/coreModule/IStableMaster.sol new file mode 100644 index 0000000..0e39741 --- /dev/null +++ b/contracts/interfaces/coreModule/IStableMaster.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "./IPerpetualManager.sol"; +import "./IOracleCore.sol"; + +// Struct to handle all the parameters to manage the fees +// related to a given collateral pool (associated to the stablecoin) +struct MintBurnData { + // Values of the thresholds to compute the minting fees + // depending on HA hedge (scaled by `BASE_PARAMS`) + uint64[] xFeeMint; + // Values of the fees at thresholds (scaled by `BASE_PARAMS`) + uint64[] yFeeMint; + // Values of the thresholds to compute the burning fees + // depending on HA hedge (scaled by `BASE_PARAMS`) + uint64[] xFeeBurn; + // Values of the fees at thresholds (scaled by `BASE_PARAMS`) + uint64[] yFeeBurn; + // Max proportion of collateral from users that can be covered by HAs + // It is exactly the same as the parameter of the same name in `PerpetualManager`, whenever one is updated + // the other changes accordingly + uint64 targetHAHedge; + // Minting fees correction set by the `FeeManager` contract: they are going to be multiplied + // to the value of the fees computed using the hedge curve + // Scaled by `BASE_PARAMS` + uint64 bonusMalusMint; + // Burning fees correction set by the `FeeManager` contract: they are going to be multiplied + // to the value of the fees computed using the hedge curve + // Scaled by `BASE_PARAMS` + uint64 bonusMalusBurn; + // Parameter used to limit the number of stablecoins that can be issued using the concerned collateral + uint256 capOnStableMinted; +} + +// Struct to handle all the variables and parameters to handle SLPs in the protocol +// including the fraction of interests they receive or the fees to be distributed to +// them +struct SLPData { + // Last timestamp at which the `sanRate` has been updated for SLPs + uint256 lastBlockUpdated; + // Fees accumulated from previous blocks and to be distributed to SLPs + uint256 lockedInterests; + // Max interests used to update the `sanRate` in a single block + // Should be in collateral token base + uint256 maxInterestsDistributed; + // Amount of fees left aside for SLPs and that will be distributed + // when the protocol is collateralized back again + uint256 feesAside; + // Part of the fees normally going to SLPs that is left aside + // before the protocol is collateralized back again (depends on collateral ratio) + // Updated by keepers and scaled by `BASE_PARAMS` + uint64 slippageFee; + // Portion of the fees from users minting and burning + // that goes to SLPs (the rest goes to surplus) + uint64 feesForSLPs; + // Slippage factor that's applied to SLPs exiting (depends on collateral ratio) + // If `slippage = BASE_PARAMS`, SLPs can get nothing, if `slippage = 0` they get their full claim + // Updated by keepers and scaled by `BASE_PARAMS` + uint64 slippage; + // Portion of the interests from lending + // that goes to SLPs (the rest goes to surplus) + uint64 interestsForSLPs; +} + +/// @title IStableMaster +/// @author Angle Labs, Inc. +interface IStableMaster { + function agToken() external view returns (address); + + function updateStocksUsers(uint256 amount, address poolManager) external; + + function collateralMap( + address poolManager + ) + external + view + returns ( + address token, + address sanToken, + IPerpetualManager perpetualManager, + IOracleCore oracle, + uint256 stocksUsers, + uint256 sanRate, + uint256 collatBase, + SLPData memory slpData, + MintBurnData memory feeData + ); + + function paused(bytes32) external view returns (bool); + + function deposit(uint256 amount, address user, address poolManager) external; + + function withdraw(uint256 amount, address burner, address dest, address poolManager) external; +} diff --git a/contracts/interfaces/external/create2/ImmutableCreate2Factory.sol b/contracts/interfaces/external/create2/ImmutableCreate2Factory.sol new file mode 100644 index 0000000..a4cffdf --- /dev/null +++ b/contracts/interfaces/external/create2/ImmutableCreate2Factory.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +interface ImmutableCreate2Factory { + function safeCreate2(bytes32 salt, bytes memory initCode) external payable returns (address deploymentAddress); + + function findCreate2Address( + bytes32 salt, + bytes calldata initCode + ) external view returns (address deploymentAddress); + + function findCreate2AddressViaHash( + bytes32 salt, + bytes32 initCodeHash + ) external view returns (address deploymentAddress); +} diff --git a/contracts/mock/MockCoreBorrow.sol b/contracts/mock/MockCoreBorrow.sol new file mode 100644 index 0000000..b67ce73 --- /dev/null +++ b/contracts/mock/MockCoreBorrow.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "../interfaces/ICoreBorrow.sol"; +import "../interfaces/IFlashAngle.sol"; +import "../interfaces/ITreasury.sol"; + +contract MockCoreBorrow is ICoreBorrow { + mapping(address => bool) public flashLoaners; + mapping(address => bool) public governors; + mapping(address => bool) public guardians; + + function isFlashLoanerTreasury(address treasury) external view override returns (bool) { + return flashLoaners[treasury]; + } + + function isGovernor(address admin) external view override returns (bool) { + return governors[admin]; + } + + function isGovernorOrGuardian(address admin) external view override returns (bool) { + return governors[admin] || guardians[admin]; + } + + function toggleGovernor(address admin) external { + governors[admin] = !governors[admin]; + } + + function toggleGuardian(address admin) external { + guardians[admin] = !guardians[admin]; + } + + function toggleFlashLoaners(address admin) external { + flashLoaners[admin] = !flashLoaners[admin]; + } + + function addStablecoinSupport(IFlashAngle flashAngle, address _treasury) external { + flashAngle.addStablecoinSupport(_treasury); + } + + function removeStablecoinSupport(IFlashAngle flashAngle, address _treasury) external { + flashAngle.removeStablecoinSupport(_treasury); + } + + function setCore(IFlashAngle flashAngle, address _core) external { + flashAngle.setCore(_core); + } + + function setFlashLoanModule(ITreasury _treasury, address _flashLoanModule) external { + _treasury.setFlashLoanModule(_flashLoanModule); + } +} diff --git a/contracts/mock/MockFlashLoanModule.sol b/contracts/mock/MockFlashLoanModule.sol new file mode 100644 index 0000000..13bc01d --- /dev/null +++ b/contracts/mock/MockFlashLoanModule.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "../interfaces/IFlashAngle.sol"; +import "../interfaces/ICoreBorrow.sol"; + +contract MockFlashLoanModule is IFlashAngle { + ICoreBorrow public override core; + mapping(address => bool) public stablecoinsSupported; + mapping(IAgToken => uint256) public interestAccrued; + uint256 public surplusValue; + + constructor(ICoreBorrow _core) { + core = _core; + } + + function accrueInterestToTreasury(IAgToken stablecoin) external override returns (uint256 balance) { + balance = surplusValue; + interestAccrued[stablecoin] += balance; + } + + function addStablecoinSupport(address _treasury) external override { + stablecoinsSupported[_treasury] = true; + } + + function removeStablecoinSupport(address _treasury) external override { + stablecoinsSupported[_treasury] = false; + } + + function setCore(address _core) external override { + core = ICoreBorrow(_core); + } + + function setSurplusValue(uint256 _surplusValue) external { + surplusValue = _surplusValue; + } +} diff --git a/contracts/mock/MockFlashLoanReceiver.sol b/contracts/mock/MockFlashLoanReceiver.sol new file mode 100644 index 0000000..c29e49d --- /dev/null +++ b/contracts/mock/MockFlashLoanReceiver.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.7; + +import "oz/token/ERC20/ERC20.sol"; + +import "oz/interfaces/IERC3156FlashBorrower.sol"; + +import "oz/interfaces/IERC3156FlashLender.sol"; + +contract MockFlashLoanReceiver is IERC3156FlashBorrower { + bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); + + constructor() {} + + function onFlashLoan( + address, + address token, + uint256 amount, + uint256 fee, + bytes calldata data + ) external override returns (bytes32) { + IERC20(token).approve(msg.sender, amount + fee); + if (amount >= 10 ** 21) return keccak256("error"); + if (amount == 2 * 10 ** 18) { + IERC3156FlashLender(msg.sender).flashLoan(IERC3156FlashBorrower(address(this)), token, amount, data); + return keccak256("reentrant"); + } else return CALLBACK_SUCCESS; + } +} diff --git a/contracts/mock/MockLayerZero.sol b/contracts/mock/MockLayerZero.sol new file mode 100644 index 0000000..645da7d --- /dev/null +++ b/contracts/mock/MockLayerZero.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "layer-zero/interfaces/ILayerZeroReceiver.sol"; + +contract MockLayerZero { + mapping(uint16 => uint256) public counters; + uint256 public config; + mapping(uint16 => uint64) public outboundNonce; + uint256 public resumeReceived; + uint256 public sendVersion; + uint256 public receiveVersion; + + /// @notice Initiate with a fixe change rate + constructor() {} + + function send( + uint16 _dstChainId, + bytes calldata, + bytes calldata, + address, + address, + bytes calldata + ) external payable { + counters[_dstChainId] += 1; + } + + function getOutboundNonce(uint16 _dstChainId, address) external view returns (uint64) { + return outboundNonce[_dstChainId]; + } + + function setOutBoundNonce(uint16 _from, uint64 value) external { + outboundNonce[_from] = value; + } + + function lzReceive( + address lzApp, + uint16 _srcChainId, + bytes memory _srcAddress, + uint64 _nonce, + bytes memory _payload + ) public { + ILayerZeroReceiver(lzApp).lzReceive(_srcChainId, _srcAddress, _nonce, _payload); + } + + function estimateFees( + uint16, + address, + bytes calldata, + bool, + bytes calldata + ) external pure returns (uint256 nativeFee, uint256 zroFee) { + return (123, 456); + } + + function setConfig(uint16, uint16, uint256 _configType, bytes calldata) external { + config = _configType; + } + + function getConfig(uint16, uint16, address, uint256) external view returns (bytes memory) { + return abi.encodePacked(config); + } + + function setSendVersion(uint16 _version) external { + sendVersion = _version; + } + + function setReceiveVersion(uint16 _version) external { + receiveVersion = _version; + } + + function forceResumeReceive(uint16, bytes calldata) external { + resumeReceived = 1 - resumeReceived; + } +} diff --git a/contracts/mock/MockToken.sol b/contracts/mock/MockToken.sol new file mode 100644 index 0000000..a7ecb57 --- /dev/null +++ b/contracts/mock/MockToken.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.7; + +import "oz/token/ERC20/ERC20.sol"; + +contract MockToken is ERC20 { + event Minting(address indexed _to, address indexed _minter, uint256 _amount); + + event Burning(address indexed _from, address indexed _burner, uint256 _amount); + + uint8 internal _decimal; + mapping(address => bool) public minters; + address public treasury; + + constructor(string memory name_, string memory symbol_, uint8 decimal_) ERC20(name_, symbol_) { + _decimal = decimal_; + } + + function decimals() public view override returns (uint8) { + return _decimal; + } + + function mint(address account, uint256 amount) external { + _mint(account, amount); + emit Minting(account, msg.sender, amount); + } + + function burn(address account, uint256 amount) public { + _burn(account, amount); + emit Burning(account, msg.sender, amount); + } + + function setAllowance(address from, address to) public { + _approve(from, to, type(uint256).max); + } + + function burnSelf(uint256 amount, address account) public { + _burn(account, amount); + emit Burning(account, msg.sender, amount); + } + + function addMinter(address minter) public { + minters[minter] = true; + } + + function removeMinter(address minter) public { + minters[minter] = false; + } + + function setTreasury(address _treasury) public { + treasury = _treasury; + } +} diff --git a/contracts/mock/MockTokenPermit.sol b/contracts/mock/MockTokenPermit.sol new file mode 100644 index 0000000..f1ee3da --- /dev/null +++ b/contracts/mock/MockTokenPermit.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.7; + +import "oz/token/ERC20/extensions/draft-ERC20Permit.sol"; +import "oz/token/ERC20/IERC20.sol"; +import "oz/token/ERC20/utils/SafeERC20.sol"; + +contract MockTokenPermit is ERC20Permit { + using SafeERC20 for IERC20; + event Minting(address indexed _to, address indexed _minter, uint256 _amount); + + event Burning(address indexed _from, address indexed _burner, uint256 _amount); + + uint8 internal _decimal; + mapping(address => bool) public minters; + address public treasury; + uint256 public fees; + + bool public reverts; + + constructor(string memory name_, string memory symbol_, uint8 decimal_) ERC20Permit(name_) ERC20(name_, symbol_) { + _decimal = decimal_; + } + + function decimals() public view override returns (uint8) { + return _decimal; + } + + function mint(address account, uint256 amount) external { + _mint(account, amount); + emit Minting(account, msg.sender, amount); + } + + function burn(address account, uint256 amount) public { + _burn(account, amount); + emit Burning(account, msg.sender, amount); + } + + function setAllowance(address from, address to) public { + _approve(from, to, type(uint256).max); + } + + function burnSelf(uint256 amount, address account) public { + _burn(account, amount); + emit Burning(account, msg.sender, amount); + } + + function addMinter(address minter) public { + minters[minter] = true; + } + + function removeMinter(address minter) public { + minters[minter] = false; + } + + function setTreasury(address _treasury) public { + treasury = _treasury; + } + + function setFees(uint256 _fees) public { + fees = _fees; + } + + function recoverERC20(IERC20 token, address to, uint256 amount) external { + token.safeTransfer(to, amount); + } + + function swapIn(address bridgeToken, uint256 amount, address to) external returns (uint256) { + require(!reverts); + + IERC20(bridgeToken).safeTransferFrom(msg.sender, address(this), amount); + uint256 canonicalOut = amount; + canonicalOut -= (canonicalOut * fees) / 10 ** 9; + _mint(to, canonicalOut); + return canonicalOut; + } + + function swapOut(address bridgeToken, uint256 amount, address to) external returns (uint256) { + require(!reverts); + _burn(msg.sender, amount); + uint256 bridgeOut = amount; + bridgeOut -= (bridgeOut * fees) / 10 ** 9; + IERC20(bridgeToken).safeTransfer(to, bridgeOut); + return bridgeOut; + } +} diff --git a/contracts/mock/MockTreasury.sol b/contracts/mock/MockTreasury.sol new file mode 100644 index 0000000..44068cd --- /dev/null +++ b/contracts/mock/MockTreasury.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "../interfaces/ITreasury.sol"; +import "../interfaces/IFlashAngle.sol"; +import "../interfaces/IVaultManager.sol"; + +contract MockTreasury is ITreasury { + IAgToken public override stablecoin; + address public governor; + address public guardian; + address public vaultManager1; + address public vaultManager2; + address public flashLoanModule; + address[] public vaultManagerList; + + constructor( + IAgToken _stablecoin, + address _governor, + address _guardian, + address _vaultManager1, + address _vaultManager2, + address _flashLoanModule + ) { + stablecoin = _stablecoin; + governor = _governor; + guardian = _guardian; + vaultManager1 = _vaultManager1; + vaultManager2 = _vaultManager2; + flashLoanModule = _flashLoanModule; + } + + function isGovernor(address admin) external view override returns (bool) { + return (admin == governor); + } + + function isGovernorOrGuardian(address admin) external view override returns (bool) { + return (admin == governor || admin == guardian); + } + + function isVaultManager(address _vaultManager) external view override returns (bool) { + return (_vaultManager == vaultManager1 || _vaultManager == vaultManager2); + } + + function setStablecoin(IAgToken _stablecoin) external { + stablecoin = _stablecoin; + } + + function setFlashLoanModule(address _flashLoanModule) external override { + flashLoanModule = _flashLoanModule; + } + + function setGovernor(address _governor) external { + governor = _governor; + } + + function setVaultManager(address _vaultManager) external { + vaultManager1 = _vaultManager; + } + + function setVaultManager2(address _vaultManager) external { + vaultManager2 = _vaultManager; + } + + function setTreasury(address _agTokenOrVaultManager, address _treasury) external { + IAgToken(_agTokenOrVaultManager).setTreasury(_treasury); + } + + function addMinter(IAgToken _agToken, address _minter) external { + _agToken.addMinter(_minter); + } + + function removeMinter(IAgToken _agToken, address _minter) external { + _agToken.removeMinter(_minter); + } + + function accrueInterestToTreasury(IFlashAngle flashAngle) external returns (uint256 balance) { + balance = flashAngle.accrueInterestToTreasury(stablecoin); + } + + function accrueInterestToTreasuryVaultManager(IVaultManager _vaultManager) external returns (uint256, uint256) { + return _vaultManager.accrueInterestToTreasury(); + } +} diff --git a/contracts/treasury/Treasury.sol b/contracts/treasury/Treasury.sol new file mode 100644 index 0000000..63c172d --- /dev/null +++ b/contracts/treasury/Treasury.sol @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-3.0 + +/* + * █ + ***** ▓▓▓ + * ▓▓▓▓▓▓▓ + * ///. ▓▓▓▓▓▓▓▓▓▓▓▓▓ + ***** //////// ▓▓▓▓▓▓▓ + * ///////////// ▓▓▓ + ▓▓ ////////////////// █ ▓▓ + ▓▓ ▓▓ /////////////////////// ▓▓ ▓▓ + ▓▓ ▓▓ //////////////////////////// ▓▓ ▓▓ + ▓▓ ▓▓ /////////▓▓▓///////▓▓▓///////// ▓▓ ▓▓ + ▓▓ ,////////////////////////////////////// ▓▓ ▓▓ + ▓▓ ////////////////////////////////////////// ▓▓ + ▓▓ //////////////////////▓▓▓▓///////////////////// + ,//////////////////////////////////////////////////// + .////////////////////////////////////////////////////////// + .//////////////////////////██.,//////////////////////////█ + .//////////////////////████..,./////////////////////██ + ...////////////////███████.....,.////////////////███ + ,.,////////////████████ ........,///////////████ + .,.,//////█████████ ,.......///////████ + ,..//████████ ........./████ + ..,██████ .....,███ + .██ ,.,█ + + + + ▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓ ▓▓▓▓ + ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓ ▓▓ ▓▓▓▓▓ + ▓▓▓ ▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ +*/ + +pragma solidity ^0.8.12; + +import "oz-upgradeable/proxy/utils/Initializable.sol"; +import "oz/token/ERC20/utils/SafeERC20.sol"; +import "oz/token/ERC20/IERC20.sol"; + +import "../interfaces/IAgToken.sol"; +import "../interfaces/ICoreBorrow.sol"; +import "../interfaces/IFlashAngle.sol"; +import "../interfaces/ITreasury.sol"; +import "../interfaces/IVaultManager.sol"; + +/// @title Treasury +/// @author Angle Labs, Inc. +/// @notice Treasury of Angle Borrowing Module doing the accounting across all VaultManagers for +/// a given stablecoin +contract Treasury is ITreasury, Initializable { + using SafeERC20 for IERC20; + + /// @notice Base used for parameter computation + uint256 public constant BASE_9 = 1e9; + + // ================================= REFERENCES ================================ + + /// @notice Reference to the `CoreBorrow` contract of the module which handles all AccessControl logic + ICoreBorrow public core; + /// @notice Flash Loan Module with a minter right on the stablecoin + IFlashAngle public flashLoanModule; + /// @inheritdoc ITreasury + IAgToken public stablecoin; + /// @notice Address responsible for handling the surplus made by the treasury + address public surplusManager; + /// @notice List of the accepted `VaultManager` of the protocol + address[] public vaultManagerList; + /// @notice Maps an address to 1 if it was initialized as a `VaultManager` contract + mapping(address => uint256) public vaultManagerMap; + + // ================================= VARIABLES ================================= + + /// @notice Amount of bad debt (unbacked stablecoin) accumulated across all `VaultManager` contracts + /// linked to this stablecoin + uint256 public badDebt; + /// @notice Surplus amount accumulated by the contract waiting to be distributed to governance. Technically + /// only a share of this `surplusBuffer` will go to governance. Once a share of the surplus buffer has been + /// given to governance, then this surplus is reset + uint256 public surplusBuffer; + + // ================================= PARAMETER ================================= + + /// @notice Share of the `surplusBuffer` distributed to governance (in `BASE_9`) + uint64 public surplusForGovernance; + + // =================================== EVENTS ================================== + + event BadDebtUpdated(uint256 badDebtValue); + event CoreUpdated(address indexed _core); + event NewTreasurySet(address indexed _treasury); + event Recovered(address indexed token, address indexed to, uint256 amount); + event SurplusBufferUpdated(uint256 surplusBufferValue); + event SurplusForGovernanceUpdated(uint64 _surplusForGovernance); + event SurplusManagerUpdated(address indexed _surplusManager); + event VaultManagerToggled(address indexed vaultManager); + + // =================================== ERRORS ================================== + + error AlreadyVaultManager(); + error InvalidAddress(); + error InvalidTreasury(); + error NotCore(); + error NotGovernor(); + error NotVaultManager(); + error RightsNotRemoved(); + error TooBigAmount(); + error TooHighParameterValue(); + error ZeroAddress(); + + // ================================== MODIFIER ================================= + + /// @notice Checks whether the `msg.sender` has the governor role or not + modifier onlyGovernor() { + if (!core.isGovernor(msg.sender)) revert NotGovernor(); + _; + } + + /// @notice Initializes the treasury contract + /// @param _core Address of the `CoreBorrow` contract of the module + /// @param _stablecoin Address of the stablecoin + function initialize(ICoreBorrow _core, IAgToken _stablecoin) public virtual initializer { + if (address(_stablecoin) == address(0) || address(_core) == address(0)) revert ZeroAddress(); + core = _core; + stablecoin = _stablecoin; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + // =============================== VIEW FUNCTIONS ============================== + + /// @inheritdoc ITreasury + function isGovernor(address admin) external view returns (bool) { + return core.isGovernor(admin); + } + + /// @inheritdoc ITreasury + function isGovernorOrGuardian(address admin) external view returns (bool) { + return core.isGovernorOrGuardian(admin); + } + + /// @inheritdoc ITreasury + function isVaultManager(address _vaultManager) external view returns (bool) { + return vaultManagerMap[_vaultManager] == 1; + } + + // ===================== EXTERNAL PERMISSIONLESS FUNCTIONS ===================== + + /// @notice Fetches the surplus accrued across all the `VaultManager` contracts controlled by this + /// `Treasury` contract as well as from the fees of the `FlashLoan` module + /// @return Surplus buffer value at the end of the call + /// @return Bad debt value at the end of the call + /// @dev This function pools surplus and bad debt across all contracts and then updates the `surplusBuffer` + /// (or the `badDebt` if more losses were made than profits) + function fetchSurplusFromAll() external returns (uint256, uint256) { + return _fetchSurplusFromAll(); + } + + /// @notice Fetches the surplus accrued in the flash loan module and updates the `surplusBuffer` + /// @return Surplus buffer value at the end of the call + /// @return Bad debt value at the end of the call + /// @dev This function fails if the `flashLoanModule` has not been initialized yet + function fetchSurplusFromFlashLoan() external returns (uint256, uint256) { + uint256 surplusBufferValue = surplusBuffer + flashLoanModule.accrueInterestToTreasury(stablecoin); + return _updateSurplusAndBadDebt(surplusBufferValue, badDebt); + } + + /// @notice Pushes the surplus buffer to the `surplusManager` contract + /// @return governanceAllocation Amount transferred to governance + /// @dev It makes sure to fetch the surplus from all the contracts handled by this treasury to avoid + /// the situation where rewards are still distributed to governance even though a `VaultManager` has made + /// a big loss + /// @dev Typically this function is to be called once every week by a keeper to distribute rewards to veANGLE + /// holders + /// @dev `stablecoin` must be an AgToken and hence `transfer` reverts if the call is not successful + function pushSurplus() external returns (uint256 governanceAllocation) { + address _surplusManager = surplusManager; + if (_surplusManager == address(0)) { + revert ZeroAddress(); + } + (uint256 surplusBufferValue, ) = _fetchSurplusFromAll(); + surplusBuffer = 0; + emit SurplusBufferUpdated(0); + governanceAllocation = (surplusForGovernance * surplusBufferValue) / BASE_9; + stablecoin.transfer(_surplusManager, governanceAllocation); + } + + /// @notice Updates the bad debt of the protocol in case where the protocol has accumulated some revenue + /// from an external source + /// @param amount Amount to reduce the bad debt of + /// @return badDebtValue Value of the bad debt at the end of the call + /// @dev If the protocol has made a loss and managed to make some profits to recover for this loss (through + /// a program like Olympus Pro), then this function needs to be called + /// @dev `badDebt` is simply reduced here by burning stablecoins + /// @dev It is impossible to burn more than the `badDebt` otherwise this function could be used to manipulate + /// the `surplusBuffer` and hence the amount going to governance + function updateBadDebt(uint256 amount) external returns (uint256 badDebtValue) { + stablecoin.burnSelf(amount, address(this)); + badDebtValue = badDebt - amount; + badDebt = badDebtValue; + emit BadDebtUpdated(badDebtValue); + } + + // ========================= INTERNAL UTILITY FUNCTIONS ======================== + + /// @notice Internal version of the `fetchSurplusFromAll` function + function _fetchSurplusFromAll() internal returns (uint256 surplusBufferValue, uint256 badDebtValue) { + (surplusBufferValue, badDebtValue) = _fetchSurplusFromList(vaultManagerList); + // It will fail anyway if the `flashLoanModule` is the zero address + if (address(flashLoanModule) != address(0)) + surplusBufferValue += flashLoanModule.accrueInterestToTreasury(stablecoin); + (surplusBufferValue, badDebtValue) = _updateSurplusAndBadDebt(surplusBufferValue, badDebtValue); + } + + /// @notice Fetches the surplus from a list of `VaultManager` addresses without modifying the + /// `surplusBuffer` and `badDebtValue` + /// @return surplusBufferValue Value the `surplusBuffer` should have after the call if it was updated + /// @return badDebtValue Value the `badDebt` should have after the call if it was updated + /// @dev This internal function is never to be called alone, and should always be called in conjunction + /// with the `_updateSurplusAndBadDebt` function + function _fetchSurplusFromList( + address[] memory vaultManagers + ) internal returns (uint256 surplusBufferValue, uint256 badDebtValue) { + badDebtValue = badDebt; + surplusBufferValue = surplusBuffer; + uint256 newSurplus; + uint256 newBadDebt; + uint256 vaultManagersLength = vaultManagers.length; + for (uint256 i; i < vaultManagersLength; ++i) { + (newSurplus, newBadDebt) = IVaultManager(vaultManagers[i]).accrueInterestToTreasury(); + surplusBufferValue += newSurplus; + badDebtValue += newBadDebt; + } + } + + /// @notice Updates the `surplusBuffer` and the `badDebt` from updated values after calling the flash loan module + /// and/or a list of `VaultManager` contracts + /// @param surplusBufferValue Value of the surplus buffer after the calls to the different modules + /// @param badDebtValue Value of the bad debt after the calls to the different modules + /// @return Value of the `surplusBuffer` corrected from the `badDebt` + /// @return Value of the `badDebt` corrected from the `surplusBuffer` and from the surplus the treasury + /// had accumulated previously + /// @dev When calling this function, it is possible that there is a positive `surplusBufferValue` + /// and `badDebtValue`, this function tries to reconcile both values and makes sure that we either have + /// surplus or bad debt but not both at the same time + function _updateSurplusAndBadDebt( + uint256 surplusBufferValue, + uint256 badDebtValue + ) internal returns (uint256, uint256) { + if (badDebtValue != 0) { + // If we have bad debt we need to burn stablecoins that accrued to the protocol + // We still need to make sure that we're not burning too much or as much as we can if the debt is big + uint256 balance = stablecoin.balanceOf(address(this)); + // We are going to burn `min(balance, badDebtValue)` + uint256 toBurn = balance <= badDebtValue ? balance : badDebtValue; + stablecoin.burnSelf(toBurn, address(this)); + // If we burned more than `surplusBuffer`, we set surplus to 0. It means we had to tap into Treasury reserve + surplusBufferValue = toBurn >= surplusBufferValue ? 0 : surplusBufferValue - toBurn; + badDebtValue -= toBurn; + // Note here that the stablecoin balance is necessarily greater than the surplus buffer, and so if + // `surplusBuffer >= toBurn`, then `badDebtValue = toBurn` + } + surplusBuffer = surplusBufferValue; + badDebt = badDebtValue; + emit SurplusBufferUpdated(surplusBufferValue); + emit BadDebtUpdated(badDebtValue); + return (surplusBufferValue, badDebtValue); + } + + /// @notice Adds a new `VaultManager` + /// @param vaultManager `VaultManager` contract to add + /// @dev This contract should have already been initialized with a correct treasury address + /// @dev It's this function that gives the minter right to the `VaultManager` + function _addVaultManager(address vaultManager) internal virtual { + if (vaultManagerMap[vaultManager] == 1) revert AlreadyVaultManager(); + if (address(IVaultManager(vaultManager).treasury()) != address(this)) revert InvalidTreasury(); + vaultManagerMap[vaultManager] = 1; + vaultManagerList.push(vaultManager); + emit VaultManagerToggled(vaultManager); + stablecoin.addMinter(vaultManager); + } + + // ============================= GOVERNOR FUNCTIONS ============================ + + /// @notice Adds a new minter for the stablecoin + /// @param minter Minter address to add + function addMinter(address minter) external virtual onlyGovernor { + if (minter == address(0)) revert ZeroAddress(); + stablecoin.addMinter(minter); + } + + /// @notice External wrapper for `_addVaultManager` + function addVaultManager(address vaultManager) external virtual onlyGovernor { + _addVaultManager(vaultManager); + } + + /// @notice Removes a minter from the stablecoin contract + /// @param minter Minter address to remove + function removeMinter(address minter) external virtual onlyGovernor { + // To remove the minter role to a `VaultManager` you have to go through the `removeVaultManager` function + if (vaultManagerMap[minter] == 1) revert InvalidAddress(); + stablecoin.removeMinter(minter); + } + + /// @notice Removes a `VaultManager` + /// @param vaultManager `VaultManager` contract to remove + /// @dev A removed `VaultManager` loses its minter right on the stablecoin + function removeVaultManager(address vaultManager) external onlyGovernor { + if (vaultManagerMap[vaultManager] != 1) revert NotVaultManager(); + delete vaultManagerMap[vaultManager]; + // deletion from `vaultManagerList` loop + uint256 vaultManagerListLength = vaultManagerList.length; + for (uint256 i; i < vaultManagerListLength - 1; ++i) { + if (vaultManagerList[i] == vaultManager) { + // replace the `VaultManager` to remove with the last of the list + vaultManagerList[i] = vaultManagerList[vaultManagerListLength - 1]; + break; + } + } + // remove last element in array + vaultManagerList.pop(); + emit VaultManagerToggled(vaultManager); + stablecoin.removeMinter(vaultManager); + } + + /// @notice Allows to recover any ERC20 token, including the stablecoin handled by this contract, and to send it + /// to a contract + /// @param tokenAddress Address of the token to recover + /// @param to Address of the contract to send collateral to + /// @param amountToRecover Amount of collateral to transfer + /// @dev It is impossible to recover the stablecoin of the protocol if there is some bad debt for it + /// @dev In this case, the function makes sure to fetch the surplus/bad debt from all the `VaultManager` contracts + /// and from the flash loan module + /// @dev If the token to recover is the stablecoin, tokens recovered are fetched + /// from the surplus and not from the `surplusBuffer` + function recoverERC20(address tokenAddress, address to, uint256 amountToRecover) external onlyGovernor { + // Cannot recover stablecoin if badDebt or tap into the surplus buffer + if (tokenAddress == address(stablecoin)) { + _fetchSurplusFromAll(); + // If balance is non zero then this means, after the call to `fetchSurplusFromAll` that + // bad debt is necessarily null + uint256 balance = stablecoin.balanceOf(address(this)); + if (amountToRecover + surplusBuffer > balance) revert TooBigAmount(); + stablecoin.transfer(to, amountToRecover); + } else { + IERC20(tokenAddress).safeTransfer(to, amountToRecover); + } + emit Recovered(tokenAddress, to, amountToRecover); + } + + /// @notice Changes the treasury contract and communicates this change to all `VaultManager` contract + /// @param _treasury New treasury address for this stablecoin + /// @dev This function is basically a way to remove rights to this contract and grant them to a new one + /// @dev It could be used to set a new core contract + function setTreasury(address _treasury) external virtual onlyGovernor { + if (ITreasury(_treasury).stablecoin() != stablecoin) revert InvalidTreasury(); + // Flash loan role should be removed before calling this function + if (core.isFlashLoanerTreasury(address(this))) revert RightsNotRemoved(); + emit NewTreasurySet(_treasury); + uint256 vaultManagerListLength = vaultManagerList.length; + for (uint256 i; i < vaultManagerListLength; ++i) { + IVaultManager(vaultManagerList[i]).setTreasury(_treasury); + } + // A `TreasuryUpdated` event is triggered in the stablecoin + stablecoin.setTreasury(_treasury); + } + + /// @notice Sets the `surplusForGovernance` parameter + /// @param _surplusForGovernance New value of the parameter + /// @dev To pause surplus distribution, governance needs to set a zero value for `surplusForGovernance` + /// which means + function setSurplusForGovernance(uint64 _surplusForGovernance) external onlyGovernor { + if (_surplusForGovernance > BASE_9) revert TooHighParameterValue(); + surplusForGovernance = _surplusForGovernance; + emit SurplusForGovernanceUpdated(_surplusForGovernance); + } + + /// @notice Sets the `surplusManager` contract responsible for handling the surplus of the + /// protocol + /// @param _surplusManager New address responsible for handling the surplus + function setSurplusManager(address _surplusManager) external onlyGovernor { + if (_surplusManager == address(0)) revert ZeroAddress(); + surplusManager = _surplusManager; + emit SurplusManagerUpdated(_surplusManager); + } + + /// @notice Sets a new `core` contract + /// @dev This function should typically be called on all treasury contracts after the `setCore` + /// function has been called on the `CoreBorrow` contract + /// @dev One sanity check that can be performed here is to verify whether at least the governor + /// calling the contract is still a governor in the new core + function setCore(ICoreBorrow _core) external onlyGovernor { + if (!_core.isGovernor(msg.sender)) revert NotGovernor(); + core = ICoreBorrow(_core); + emit CoreUpdated(address(_core)); + } + + /// @inheritdoc ITreasury + function setFlashLoanModule(address _flashLoanModule) external { + if (msg.sender != address(core)) revert NotCore(); + address oldFlashLoanModule = address(flashLoanModule); + flashLoanModule = IFlashAngle(_flashLoanModule); + if (oldFlashLoanModule != address(0)) { + stablecoin.removeMinter(oldFlashLoanModule); + } + // We may want to cancel the module + if (_flashLoanModule != address(0)) { + stablecoin.addMinter(_flashLoanModule); + } + } +} diff --git a/contracts/utils/Constants.sol b/contracts/utils/Constants.sol new file mode 100644 index 0000000..93344b3 --- /dev/null +++ b/contracts/utils/Constants.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +error InsufficientAssets(); + +/// @title Constants +/// @author Angle Labs, Inc. +/// @notice Constants and errors for Angle Protocol contracts +contract Constants { + uint256 internal constant _BASE_9 = 1e9; + uint256 internal constant _BASE_18 = 1e18; + uint256 internal constant _BASE_27 = 1e27; + uint256 internal constant _BASE_36 = 1e36; +} diff --git a/foundry.toml b/foundry.toml index 25b918f..07fba83 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,73 @@ -[profile.default] -src = "src" -out = "out" -libs = ["lib"] - -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +[profile.default] +src = "contracts" +out = "out" +test = "test" +libs = ["lib"] +script = "scripts" +cache_path = "cache-forge" +gas_reports = ["*"] +via_ir = true +sizes = true +optimizer = true +optimizer_runs = 1000 +solc_version = "0.8.22" +ffi = true + +[fuzz] +runs = 10000 + +[invariant] +runs = 1000 +depth = 30 + +[rpc_endpoints] +arbitrum = "${ETH_NODE_URI_ARBITRUM}" +gnosis = "${ETH_NODE_URI_GNOSIS}" +mainnet = "${ETH_NODE_URI_MAINNET}" +optimism = "${ETH_NODE_URI_OPTIMISM}" +polygon = "${ETH_NODE_URI_POLYGON}" +fork = "${ETH_NODE_URI_FORK}" +avalanche = "${ETH_NODE_URI_AVALANCHE}" +celo = "${ETH_NODE_URI_CELO}" +polygonzkevm = "${ETH_NODE_URI_POLYGONZKEVM}" +bsc = "${ETH_NODE_URI_BSC}" +base = "${ETH_NODE_URI_BASE}" + +[etherscan] +arbitrum = { key = "${ARBITRUM_ETHERSCAN_API_KEY}" } +gnosis = { key = "${GNOSIS_ETHERSCAN_API_KEY}" , url = "https://api.gnosisscan.io/api"} +mainnet = { key = "${MAINNET_ETHERSCAN_API_KEY}" } +optimism = { key = "${OPTIMISM_ETHERSCAN_API_KEY}" } +polygon = { key = "${POLYGON_ETHERSCAN_API_KEY}" } +avalanche = { key = "${AVALANCHE_ETHERSCAN_API_KEY}" } +celo = { key = "${CELO_ETHERSCAN_API_KEY}", url = "https://api.celoscan.io/api" } +base = { key = "${BASE_ETHERSCAN_API_KEY}", url = "https://api.basescan.org/api" } +polygonzkevm = { key = "${POLYGONZKEVM_ETHERSCAN_API_KEY}", url = "https://api-zkevm.polygonscan.com/api" } +bsc = { key = "${BSC_ETHERSCAN_API_KEY}"} + +[profile.dev] +optimizer = true +via_ir = false +src = "test" +gas_reports = ["*"] + +[profile.dev.fuzz] +runs = 2000 + +[profile.dev.invariant] +runs = 10 +depth = 1 +fail_on_revert = false + +[profile.ci] +src = "test" +via_ir = false +gas_reports = ["*"] + +[profile.ci.fuzz] +runs = 100 + +[profile.ci.invariant] +runs = 10 +depth = 30 +fail_on_revert = false diff --git a/helpers/fork.sh b/helpers/fork.sh new file mode 100644 index 0000000..b686d16 --- /dev/null +++ b/helpers/fork.sh @@ -0,0 +1,73 @@ +#! /bin/bash + +function option_to_uri { + option=$1 + + case $option in + "1") + echo $ETH_NODE_URI_MAINNET + ;; + "2") + echo $ETH_NODE_URI_ARBITRUM + ;; + "3") + echo $ETH_NODE_URI_POLYGON + ;; + "4") + echo $ETH_NODE_URI_GNOSIS + ;; + "5") + echo $ETH_NODE_URI_AVALANCHE + ;; + "6") + echo $ETH_NODE_URI_BASE + ;; + "7") + echo $ETH_NODE_URI_BSC + ;; + "8") + echo $ETH_NODE_URI_CELO + ;; + "9") + echo $ETH_NODE_URI_POLYGON_ZKEVM + ;; + "10") + echo $ETH_NODE_URI_OPTIMISM + ;; + *) + ;; + esac +} + +function main { + if [ ! -f .env ]; then + echo ".env not found!" + exit 1 + fi + source .env + + echo "Which chain would you like to fork ?" + echo "- 1: Ethereum Mainnet" + echo "- 2: Arbitrum" + echo "- 3: Polygon" + echo "- 4: Gnosis" + echo "- 5: Avalanche" + echo "- 6: Base" + echo "- 7: Binance Smart Chain" + echo "- 8: Celo" + echo "- 9: Polygon ZkEvm" + echo "- 10: Optimism" + + read option + + uri=$(option_to_uri $option) + if [ -z "$uri" ]; then + echo "Unknown network" + exit 1 + fi + + echo "Forking $uri" + anvil --fork-url $uri +} + +main diff --git a/lib/LayerZero b/lib/LayerZero new file mode 160000 index 0000000..48c21c3 --- /dev/null +++ b/lib/LayerZero @@ -0,0 +1 @@ +Subproject commit 48c21c3921931798184367fc02d3a8132b041942 diff --git a/lib/chainlink b/lib/chainlink new file mode 160000 index 0000000..7680667 --- /dev/null +++ b/lib/chainlink @@ -0,0 +1 @@ +Subproject commit 7680667f309b13ba1d336bd1680026ff6f6d575f diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..ecd2ca2 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..0a2cb9a --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 0a2cb9a445c365870ed7a8ab461b12acf3e27d63 diff --git a/lib/utils b/lib/utils new file mode 160000 index 0000000..41388c6 --- /dev/null +++ b/lib/utils @@ -0,0 +1 @@ +Subproject commit 41388c6ea9c45b05b92155356d6e700802fdb566 diff --git a/package.json b/package.json new file mode 100644 index 0000000..b7d42e4 --- /dev/null +++ b/package.json @@ -0,0 +1,53 @@ +{ + "name": "ag-tokens", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "ci:coverage": "forge coverage --report lcov && yarn lcov:clean", + "coverage": "FOUNDRY_PROFILE=dev forge coverage --report lcov && yarn lcov:clean && yarn lcov:generate-html", + "compile": "forge build", + "compile:dev": "FOUNDRY_PROFILE=dev forge build", + "deploy": "forge script --skip test --broadcast --verify --slow -vvvv --rpc-url", + "deploy:fork": "forge script --skip test --slow --fork-url fork --broadcast -vvvv", + "generate": "FOUNDRY_PROFILE=dev forge script scripts/utils/GenerateSelectors.s.sol", + "deploy:check": "FOUNDRY_PROFILE=dev forge script --fork-url fork scripts/test/CheckTransmuter.s.sol", + "gas": "FOUNDRY_PROFILE=dev yarn test --gas-report", + "fork": "bash helpers/fork.sh", + "run": "docker run -it --rm -v $(pwd):/app -w /app ghcr.io/foundry-rs/foundry sh", + "script:fork": "FOUNDRY_PROFILE=dev forge script --skip test --fork-url fork --broadcast -vvvv", + "test:unit": "forge test -vvvv --gas-report --match-path \"test/units/**/*.sol\"", + "test:invariant": "forge test -vvv --gas-report --match-path \"test/invariants/**/*.sol\"", + "test:fuzz": "forge test -vvv --gas-report --match-path \"test/fuzz/**/*.sol\"", + "slither": "chmod +x ./slither.sh && ./slither.sh", + "test": "forge test -vvvv", + "lcov:clean": "lcov --remove lcov.info -o lcov.info 'test/**' 'scripts/**' 'contracts/transmuter/configs/**' 'contracts/utils/**'", + "lcov:generate-html": "genhtml lcov.info --output=coverage", + "size": "forge build --skip test --sizes", + "size:dev": "FOUNDRY_PROFILE=dev forge build --skip test --sizes", + "prettier": "prettier --write '**/*.sol'", + "lint": "yarn lint:check --fix", + "lint:check": "solhint --max-warnings 20 \"**/*.sol\"", + "vanity": "forge script --skip test --slow -vvvv --rpc-url mainnet ./scripts/utils/VanityAddress.s.sol", + "verify": "forge verify-contract --num-of-optimizations 1000 --watch --constructor-args 0000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 --compiler-version v0.8.19+commit.7dd6d404 0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776 lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy --chain" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/AngleProtocol/AgTokens.git" + }, + "keywords": [], + "author": "Angle Core Team", + "license": "GPL-3.0", + "bugs": { + "url": "https://github.com/AngleProtocol/AgTokens/issues" + }, + "devDependencies": { + "@angleprotocol/sdk": "0.38.8", + "prettier": "^2.0.0", + "prettier-plugin-solidity": "^1.1.3", + "solhint": "^3.5.1", + "solhint-plugin-prettier": "^0.0.5" + }, + "dependencies": { + } +} diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..c0843c1 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,9 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +oz/=lib/openzeppelin-contracts/contracts/ +oz-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +chainlink/=lib/chainlink/contracts/ +utils/=lib/utils/ +contracts/=contracts/ +chainlink/=lib/chainlink/contracts/ +layer-zero/=lib/LayerZero/contracts/ \ No newline at end of file diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index df9ee8b..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/scripts/BasicScript.s.sol b/scripts/BasicScript.s.sol new file mode 100644 index 0000000..539bf0c --- /dev/null +++ b/scripts/BasicScript.s.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import "forge-std/Script.sol"; +import "contracts/mock/MockToken.sol"; +import { console } from "forge-std/console.sol"; + +contract MyScript is Script { + function run() external { + vm.startBroadcast(); + + MockToken token = new MockToken("Name", "SYM", 18); + address _sender = address(uint160(uint256(keccak256(abi.encodePacked("sender"))))); + address _receiver = address(uint160(uint256(keccak256(abi.encodePacked("receiver"))))); + + console.log(address(token)); + + // deal(address(token), _sender, 1 ether); + // vm.prank(_sender); + // token.transfer(_receiver, 1 ether); + + vm.stopBroadcast(); + } +} diff --git a/scripts/utils/Constants.s.sol b/scripts/utils/Constants.s.sol new file mode 100644 index 0000000..f57822c --- /dev/null +++ b/scripts/utils/Constants.s.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.12; + +import "contracts/utils/Constants.sol"; +import "utils/src/Constants.sol"; + +/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + MAINNET CONSTANTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + +address constant DEPLOYER = 0xfdA462548Ce04282f4B6D6619823a7C64Fdc0185; diff --git a/scripts/utils/FindInitCode.s.sol b/scripts/utils/FindInitCode.s.sol new file mode 100644 index 0000000..b0e93b4 --- /dev/null +++ b/scripts/utils/FindInitCode.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.12; + +import { console } from "forge-std/console.sol"; +import "forge-std/Script.sol"; +import { StdAssertions } from "forge-std/Test.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import "stringutils/strings.sol"; +import "./Constants.s.sol"; + +import { TransparentUpgradeableProxy } from "oz/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import { ImmutableCreate2Factory } from "contracts/interfaces/external/create2/ImmutableCreate2Factory.sol"; + +/// @dev Script to run to find the init code of a contract to get a vanity address from it +contract FindInitCode is Script, StdAssertions { + using stdJson for string; + using strings for *; + + function run() external { + // To maintain chain consistency, we do as if we deployed with the deployer as a proxyAdmin before transferring + // to another address + // We use a contract that is widely deployed across many chains as an implementation to make it resilient + // to possible implementation changes + bytes memory emptyData; + bytes memory initCode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(IMMUTABLE_CREATE2_FACTORY_ADDRESS, DEPLOYER, emptyData) + ); + console.log("Proxy bytecode"); + console.logBytes(initCode); + console.logBytes(abi.encode(IMMUTABLE_CREATE2_FACTORY_ADDRESS, DEPLOYER, emptyData)); + console.log(""); + } +} diff --git a/scripts/utils/VanityAddress.s.sol b/scripts/utils/VanityAddress.s.sol new file mode 100644 index 0000000..6608a0f --- /dev/null +++ b/scripts/utils/VanityAddress.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.12; + +import "stringutils/strings.sol"; +import "forge-std/Script.sol"; +import { StdAssertions } from "forge-std/Test.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { console } from "forge-std/console.sol"; +import { VanityAddress } from "utils/src/VanityAddress.sol"; +import "./Constants.s.sol"; + +contract VanityAddressScript is Script, VanityAddress { + using stdJson for string; + + string constant JSON_VANITY_PATH = "./scripts/vanity.json"; + + using stdJson for string; + + function run() external { + // Deploy diamond + bytes + memory initCode = hex"60406080815262000f5f80380380620000188162000364565b9283398101906060818303126200035f576200003481620003a0565b9160209262000045848401620003a0565b8584015190936001600160401b0391908282116200035f57019280601f850112156200035f57835193620000836200007d86620003b5565b62000364565b94808652878601928882840101116200035f578288620000a49301620003d1565b823b1562000305577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b03199081166001600160a01b0386811691821790935590959194600093909290917fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b8580a2805115801590620002fd575b620001f5575b50505050507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103937f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f86865493815196818616885216958684820152a18315620001a357501617905551610b0a9081620004558239f35b60849086519062461bcd60e51b82526004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b8951946060860190811186821017620002e9578a52602785527f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c89860152660819985a5b195960ca1b8a860152823b156200029657928092819262000280969551915af43d156200028c573d620002706200007d82620003b5565b9081528092893d92013e620003f6565b5038808080806200012d565b60609150620003f6565b895162461bcd60e51b8152600481018a9052602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608490fd5b634e487b7160e01b85526041600452602485fd5b508362000127565b865162461bcd60e51b815260048101879052602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608490fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200038a57604052565b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200035f57565b6001600160401b0381116200038a57601f01601f191660200190565b60005b838110620003e55750506000910152565b8181015183820152602001620003d4565b9091901562000403575090565b815115620004145750805190602001fd5b6044604051809262461bcd60e51b825260206004830152620004468151809281602486015260208686019101620003d1565b601f01601f19168101030190fdfe6080604052600436101561002c575b361561001f575b61001d6104dd565b005b6100276104dd565b610015565b6000803560e01c9081633659cfe614610093575080634f1ef2861461008a5780635c60da1b146100815780638f283970146100785763f851a4400361000e57610073610455565b61000e565b506100736102f0565b5061007361023b565b50610073610157565b3461012c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012c576100ca61012f565b73ffffffffffffffffffffffffffffffffffffffff7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146000146101235761012090610117610639565b908382526106f3565b80f35b506101206104dd565b80fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361015257565b600080fd5b5060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101525761018a61012f565b60243567ffffffffffffffff9182821161015257366023830112156101525781600401359283116101525736602484840101116101525773ffffffffffffffffffffffffffffffffffffffff7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331460001461023057600060208480602461021e61021961001d996106aa565b610666565b96828852018387013784010152610833565b50505061001d6104dd565b50346101525760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610152576020600073ffffffffffffffffffffffffffffffffffffffff90817fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103541633146000146102e25750807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416905b60405191168152f35b906102eb6104dd565b6102d9565b50346101525760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101525761032861012f565b73ffffffffffffffffffffffffffffffffffffffff907fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610391808354163314600014610230577f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f604084549281519481851686521693846020820152a181156103d1577fffffffffffffffffffffffff000000000000000000000000000000000000000016179055005b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152fd5b50346101525760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610152576020600073ffffffffffffffffffffffffffffffffffffffff7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038181541633146000146104d85754604051911681529050f35b506102eb5b5073ffffffffffffffffffffffffffffffffffffffff807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035416331461055f577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54166000808092368280378136915af43d82803e1561055b573d90f35b3d90fd5b60a46040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f7879207461726760648201527f65740000000000000000000000000000000000000000000000000000000000006084820152fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051906020820182811067ffffffffffffffff82111761065957604052565b610661610609565b604052565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f604051930116820182811067ffffffffffffffff82111761065957604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff81116106e6575b01160190565b6106ee610609565b6106e0565b803b156107af5773ffffffffffffffffffffffffffffffffffffffff81167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc817fffffffffffffffffffffffff00000000000000000000000000000000000000008254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906107a7575b610792575050565b6107a49161079e6108d9565b91610957565b50565b50600061078a565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e7472616374000000000000000000000000000000000000006064820152fd5b803b156107af5773ffffffffffffffffffffffffffffffffffffffff81167f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc817fffffffffffffffffffffffff00000000000000000000000000000000000000008254161790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28151158015906108d157610792575050565b50600161078a565b604051906060820182811067ffffffffffffffff82111761094a575b604052602782527f206661696c6564000000000000000000000000000000000000000000000000006040837f416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c60208201520152565b610952610609565b6108f5565b9190823b156109a0576000816109959460208394519201905af43d15610998573d90610985610219836106aa565b9182523d6000602084013e610a24565b90565b606090610a24565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f60448201527f6e747261637400000000000000000000000000000000000000000000000000006064820152fd5b90919015610a30575090565b815115610a405750805190602001fd5b604051907f08c379a000000000000000000000000000000000000000000000000000000000825281602080600483015282519283602484015260005b848110610abd575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000604480968601015201168101030190fd5b818101830151868201604401528593508201610a7c56fea26469706673582212206eca7257d81e920296a8c93c0ac0d93d9bb0927ce3e4cefa34ff129bc0cdbceb64736f6c634300081100330000000000000000000000000000000000ffe8b47b3e2130213b802212439497000000000000000000000000fda462548ce04282f4b6d6619823a7c64fdc018500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"; + string memory json = vm.readFile(JSON_VANITY_PATH); + uint256 initInt = json.readUint(string.concat("$.", "init")); + uint256 iterations = 3000000; + + (address computed, uint256 found) = minePrefix(initCode, DEPLOYER, 0x000020, initInt, iterations); + console.log("Computed: ", computed); + console.log("Found: ", found); + + // write result to json vanity path + json = ""; + vm.serializeUint(json, "init", found); + string memory finalJson = vm.serializeAddress(json, "salt", computed); + vm.writeFile(JSON_VANITY_PATH, finalJson); + } +} diff --git a/scripts/vanity.json b/scripts/vanity.json new file mode 100644 index 0000000..da1671c --- /dev/null +++ b/scripts/vanity.json @@ -0,0 +1,4 @@ +{ + "init": 8223543, + "salt": "0xfda462548ce04282f4b6d6619823a7c64fdc01850000000000000000007d7b37" +} diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 0000000..7180139 --- /dev/null +++ b/slither.config.json @@ -0,0 +1,10 @@ +{ + "detectors_to_exclude": "naming-convention,solc-version", + "filter_paths": "(lib|test|external|node_modules|scripts)", + "solc_remaps": [ + "ds-test/=lib/ds-test/src/", + "forge-std/=lib/forge-std/src/", + "oz/=lib/openzeppelin-contracts/contracts/", + "oz-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" + ] +} diff --git a/slither.sh b/slither.sh new file mode 100755 index 0000000..19869b6 --- /dev/null +++ b/slither.sh @@ -0,0 +1,42 @@ +# Use git grep to find all files containing '0.8.19' +files=$(git grep -l '0.8.19') + +# Loop over files and replace '0.8.19' with '0.8.18' +for file in $files +do + sed -i '' 's/0.8.19/0.8.18/g' "$file" +done + +# # # Ignore test files +# find "test/" -type f -name "*.sol" | while read file; do +# # Replace '.sol' extension with '.txt' +# mv "$file" "${file%.sol}.txt" +# done + +# mv "test/mock/MockLib.txt" "test/mock/MockLib.sol" + +# Function to handle error +reset() { + # find "test/" -type f -name "*.txt" | while read file; do + # # Replace '.sol' extension with '.txt' + # mv "$file" "${file%.txt}.sol" + # done + + files=$(git grep -l '0.8.18') + for file in $files + do + sed -i '' 's/0.8.18/0.8.19/g' "$file" + done +} + +trap 'reset' ERR + +# pip3 install slither-analyzer +# pip3 install solc-select +# solc-select install 0.8.18 +# solc-select use 0.8.18 +FOUNDRY_PROFILE=dev forge build --skip test + +slither test/mock/Slither.sol --show-ignored-findings --foundry-ignore-compile + +reset \ No newline at end of file diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/fuzz/.gitkeep b/test/fuzz/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/invariants/.gitkeep b/test/invariants/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/units/BaseTest.t.sol b/test/units/BaseTest.t.sol new file mode 100644 index 0000000..00e51ec --- /dev/null +++ b/test/units/BaseTest.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.12; + +import { Test, stdMath, StdStorage, stdStorage } from "forge-std/Test.sol"; +import "../../contracts/external/ProxyAdmin.sol"; +import "../../contracts/external/TransparentUpgradeableProxy.sol"; +import "../../contracts/mock/MockCoreBorrow.sol"; +import { Math } from "oz/utils/math/Math.sol"; +import { console } from "forge-std/console.sol"; +import { CommonUtils } from "utils/src/CommonUtils.sol"; + +contract BaseTest is Test, CommonUtils { + ProxyAdmin public proxyAdmin; + MockCoreBorrow public coreBorrow; + + address internal constant _GOVERNOR = 0xdC4e6DFe07EFCa50a197DF15D9200883eF4Eb1c8; + address internal constant _GUARDIAN = 0x0C2553e4B9dFA9f83b1A6D3EAB96c4bAaB42d430; + address internal constant _KEEPER = address(uint160(uint256(keccak256(abi.encodePacked("_keeper"))))); + address internal constant _ANGLE = 0x31429d1856aD1377A8A0079410B297e1a9e214c2; + address internal constant _GOVERNOR_POLYGON = 0xdA2D2f638D6fcbE306236583845e5822554c02EA; + + address internal constant _alice = address(uint160(uint256(keccak256(abi.encodePacked("_alice"))))); + address internal constant _bob = address(uint160(uint256(keccak256(abi.encodePacked("_bob"))))); + address internal constant _charlie = address(uint160(uint256(keccak256(abi.encodePacked("_charlie"))))); + address internal constant _dylan = address(uint160(uint256(keccak256(abi.encodePacked("_dylan"))))); + + uint256 internal _ethereum; + uint256 internal _polygon; + + uint256 public constant BASE_PARAMS = 10 ** 9; + uint256 public constant BASE_STAKER = 10 ** 36; + uint256 public constant BASE_18 = 10 ** 18; + + function setUp() public virtual { + proxyAdmin = new ProxyAdmin(); + coreBorrow = new MockCoreBorrow(); + coreBorrow.toggleGuardian(_GUARDIAN); + coreBorrow.toggleGovernor(_GOVERNOR); + vm.label(_GOVERNOR, "Governor"); + vm.label(_GUARDIAN, "Guardian"); + vm.label(_alice, "Alice"); + vm.label(_bob, "Bob"); + vm.label(_charlie, "Charlie"); + vm.label(_dylan, "Dylan"); + } +} diff --git a/test/units/agToken/AgToken.t.sol b/test/units/agToken/AgToken.t.sol new file mode 100644 index 0000000..3e293df --- /dev/null +++ b/test/units/agToken/AgToken.t.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { stdStorage, StdStorage } from "forge-std/Test.sol"; +import "../BaseTest.t.sol"; +import { IAgToken, AgToken } from "contracts/agToken/AgToken.sol"; +import { MockTreasury } from "contracts/mock/MockTreasury.sol"; + +contract AgTokenTest is BaseTest { + using stdStorage for StdStorage; + + address internal _hacker = address(uint160(uint256(keccak256(abi.encodePacked("hacker"))))); + + AgToken internal _agToken; + MockTreasury internal _treasury; + AgToken internal _agTokenImplem; + + string constant _NAME = "Angle stablecoin gold"; + string constant _SYMBOL = "agGold"; + + function setUp() public override { + super.setUp(); + + _agTokenImplem = new AgToken(); + _agToken = AgToken(_deployUpgradeable(address(proxyAdmin), address(_agTokenImplem), "")); + + _treasury = new MockTreasury( + IAgToken(address(_agToken)), + _GOVERNOR, + _GUARDIAN, + address(0), + address(0), + address(0) + ); + + _agToken.initialize(_NAME, _SYMBOL, address(_treasury)); + + vm.startPrank(_GOVERNOR); + _treasury.setStablecoin(_agToken); + _treasury.addMinter(_agToken, _alice); + vm.stopPrank(); + } + + // ================================= INITIALIZE ================================ + + function test_initialize_Constructor() public { + assertEq(_agToken.name(), _NAME); + assertEq(_agToken.symbol(), _SYMBOL); + assertEq(_agToken.decimals(), 18); + assertEq(_agToken.treasury(), address(_treasury)); + } + + function test_initialize_AlreadyInitalizeFail() public { + string memory name2 = "Angle stablecoin XXX"; + string memory symbol2 = "agXXX"; + vm.expectRevert(); + _agToken.initialize(name2, symbol2, _alice); + } + + function test_initialize_WrongTreasuryAddress() public { + string memory name2 = "Angle stablecoin XXX"; + string memory symbol2 = "agXXX"; + MockTreasury treasury = new MockTreasury( + IAgToken(address(0)), + address(0), + address(0), + address(0), + address(0), + address(0) + ); + bytes memory emptyData; + _agToken = AgToken(_deployUpgradeable(address(proxyAdmin), address(_agTokenImplem), emptyData)); + + vm.expectRevert(AgToken.InvalidTreasury.selector); + _agToken.initialize(name2, symbol2, address(treasury)); + } + + // ================================= MINT ================================ + + function test_mint_WrongSender() public { + vm.expectRevert(AgToken.NotMinter.selector); + vm.prank(_bob); + _agToken.mint(_hacker, 1e18); + } + + function test_mint_ZeroAddress() public { + vm.expectRevert("ERC20: mint to the zero address"); + vm.prank(_alice); + _agToken.mint(address(0), 1e18); + } + + function test_mint_Normal() public { + uint256 amount = 1e18; + vm.prank(_alice); + _agToken.mint(_alice, amount); + assertEq(_agToken.balanceOf(_alice), amount); + assertEq(_agToken.totalSupply(), amount); + } + + // ================================= BurnStablecoin ================================ + + function test_burnStablecoin_BurnStablecoin() public { + uint256 amount = 1e18; + vm.startPrank(_alice); + _agToken.mint(_alice, amount); + _agToken.burnStablecoin(amount); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_alice), 0); + assertEq(_agToken.totalSupply(), 0); + } + + function test_burnStablecoin_BurnGreaterThanBalance() public { + uint256 amount = 1e18; + vm.startPrank(_alice); + _agToken.mint(_alice, amount); + vm.expectRevert("ERC20: burn amount exceeds balance"); + _agToken.burnStablecoin(amount + 1); + vm.stopPrank(); + } + + // ================================= BurnSelf ================================ + + function test_burnSelf_NotMinter() public { + vm.expectRevert(AgToken.NotMinter.selector); + _agToken.burnSelf(1e18, _alice); + } + + function test_burnSelf_Normal() public { + uint256 amount = 1e18; + vm.startPrank(_alice); + _agToken.mint(_alice, amount); + _agToken.burnSelf(amount, _alice); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_alice), 0); + assertEq(_agToken.totalSupply(), 0); + } + + // ================================= BurnFrom ================================ + + function test_burnFrom_NotMinter() public { + vm.prank(_bob); + vm.expectRevert(AgToken.NotMinter.selector); + _agToken.burnFrom(1e18, _bob, _alice); + } + + function test_burnFrom_NoApproval() public { + vm.prank(_alice); + vm.expectRevert(AgToken.BurnAmountExceedsAllowance.selector); + _agToken.burnFrom(1e18, _alice, _bob); + } + + function test_burnFrom_WithApproval() public { + uint256 amount = 1e18; + vm.startPrank(_alice); + _agToken.mint(_alice, amount); + _agToken.approve(_bob, amount * 2); + vm.stopPrank(); + assertEq(_agToken.allowance(_alice, _bob), amount * 2); + + vm.prank(_alice); + _agToken.burnFrom(amount, _alice, _bob); + + assertEq(_agToken.balanceOf(_alice), 0); + assertEq(_agToken.totalSupply(), 0); + assertEq(_agToken.allowance(_alice, _bob), amount); + } + + function test_burnFrom_WithoutApprovalBurnerIsSender() public { + uint256 amount = 1e18; + vm.startPrank(_alice); + _agToken.mint(_alice, amount); + _agToken.burnFrom(amount, _alice, _alice); + vm.stopPrank(); + + assertEq(_agToken.balanceOf(_alice), 0); + assertEq(_agToken.totalSupply(), 0); + } + + // ================================= AddMinter ================================ + + function test_addMinter_NotTreasury() public { + vm.expectRevert(AgToken.NotTreasury.selector); + _agToken.addMinter(_alice); + } + + function test_addMinter_MinterToggled() public { + vm.prank(_GOVERNOR); + _treasury.addMinter(_agToken, _alice); + assert(_agToken.isMinter(_alice)); + } + + // ================================= RemoveMinter ================================ + + function test_removeMinter_NotTreasury() public { + vm.expectRevert(AgToken.InvalidSender.selector); + _agToken.removeMinter(_alice); + } + + function test_removeMinter_Normal() public { + vm.prank(_GOVERNOR); + _treasury.addMinter(_agToken, _bob); + assertTrue(_agToken.isMinter(_bob)); + + vm.prank(_GOVERNOR); + _treasury.removeMinter(_agToken, _bob); + assertFalse(_agToken.isMinter(_bob)); + } + + // ================================= SetTreasury ================================ + + function test_setTreasury_NotTreasury() public { + vm.expectRevert(AgToken.NotTreasury.selector); + vm.prank(_alice); + _agToken.setTreasury(_alice); + } + + function test_setTreasury_Normal() public { + vm.prank(_GOVERNOR); + _treasury.setTreasury(address(_agToken), _alice); + + assertEq(_agToken.treasury(), _alice); + } + + function test_setTreasury_NormalReset() public { + vm.prank(_GOVERNOR); + _treasury.setTreasury(address(_agToken), _alice); + vm.prank(_alice); + _agToken.setTreasury(address(_treasury)); + + assertEq(_agToken.treasury(), address(_treasury)); + } +} diff --git a/test/units/agToken/AgTokenSideChainMultiBridge.t.sol b/test/units/agToken/AgTokenSideChainMultiBridge.t.sol new file mode 100644 index 0000000..63d78c0 --- /dev/null +++ b/test/units/agToken/AgTokenSideChainMultiBridge.t.sol @@ -0,0 +1,671 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "../BaseTest.t.sol"; +import { MockTreasury } from "contracts/mock/MockTreasury.sol"; +import { AgTokenSideChainMultiBridge } from "contracts/agToken/AgTokenSideChainMultiBridge.sol"; +import { MockToken } from "contracts/mock/MockToken.sol"; + +contract AgTokenSideChainMultiBridgeTest is BaseTest { + AgTokenSideChainMultiBridge internal _agToken; + AgTokenSideChainMultiBridge internal _agTokenImplem; + MockTreasury internal _treasury; + MockToken internal _bridgeToken; + + function setUp() public override { + super.setUp(); + + _agTokenImplem = new AgTokenSideChainMultiBridge(); + _agToken = AgTokenSideChainMultiBridge(_deployUpgradeable(address(proxyAdmin), address(_agTokenImplem), "")); + + _treasury = new MockTreasury( + IAgToken(address(_agToken)), + _GOVERNOR, + _GUARDIAN, + address(0), + address(0), + address(0) + ); + + _agToken.initialize("agEUR", "agEUR", address(_treasury)); + + _treasury.addMinter(_agToken, _alice); + _treasury.addMinter(_agToken, _GOVERNOR); + vm.prank(_alice); + _agToken.mint(_alice, 1e18); + + _bridgeToken = new MockToken("any-agEUR", "any-agEUR", 18); + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(_bridgeToken), 10e18, 1e18, 5e8, false); + } + + // ================================= INITIALIZE ================================ + + function test_initialize_Constructor() public view { + (uint256 limit, uint256 hourlyLimit, uint64 fee, bool allowed, bool paused) = _agToken.bridges( + address(_bridgeToken) + ); + assertEq(limit, 10e18); + assertEq(hourlyLimit, 1e18); + assertEq(fee, 5e8); + assertTrue(allowed); + assertFalse(paused); + assertEq(_agToken.bridgeTokensList(0), address(_bridgeToken)); + assertEq(_agToken.allBridgeTokens()[0], address(_bridgeToken)); + } + + function test_initialize_NotGovernor() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernor.selector); + vm.prank(_bob); + _agToken.addBridgeToken(address(_bridgeToken), 10e18, 1e18, 5e8, false); + } + + function test_initialize_TooHighParameterValue() public { + MockToken bridgeToken2 = new MockToken("any-agEUR", "any-agEUR", 18); + vm.expectRevert(AgTokenSideChainMultiBridge.TooHighParameterValue.selector); + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(bridgeToken2), 1e18, 1e17, 2e9, false); + } + + function test_initialize_ZeroAddress() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(0), 1e18, 1e17, 5e8, false); + } + + function test_initialize_AlreadyAdded() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(_bridgeToken), 1e18, 1e17, 5e8, false); + } + + function test_initialize_SecondTokenAdded() public { + MockToken bridgeToken2 = new MockToken("synapse-agEUR", "synapse-agEUR", 18); + + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(bridgeToken2), 1e18, 1e17, 5e8, false); + (uint256 limit, uint256 hourlyLimit, uint64 fee, bool allowed, bool paused) = _agToken.bridges( + address(bridgeToken2) + ); + assertEq(limit, 1e18); + assertEq(hourlyLimit, 1e17); + assertEq(fee, 5e8); + assertTrue(allowed); + assertFalse(paused); + assertEq(_agToken.bridgeTokensList(1), address(bridgeToken2)); + assertEq(_agToken.allBridgeTokens()[1], address(bridgeToken2)); + } + + // ================================= AddBridgeToken ================================ + + function test_addBridgeToken_Normal() public view { + (uint256 limit, uint256 hourlyLimit, uint64 fee, bool allowed, bool paused) = _agToken.bridges( + address(_bridgeToken) + ); + + assertEq(limit, 10e18); + assertEq(hourlyLimit, 1e18); + assertEq(fee, 5e8); + assertTrue(allowed); + assertFalse(paused); + assertEq(_agToken.bridgeTokensList(0), address(_bridgeToken)); + assertEq(_agToken.allBridgeTokens()[0], address(_bridgeToken)); + } + + function test_addBridgeToken_NotGovernor() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernor.selector); + vm.prank(_alice); + _agToken.addBridgeToken(address(_bridgeToken), 10e18, 1e18, 5e8, false); + } + + function test_addBridgeToken_TooHighParameterValue() public { + MockToken bridgeToken2 = new MockToken("any-agEUR", "any-agEUR", 18); + vm.expectRevert(AgTokenSideChainMultiBridge.TooHighParameterValue.selector); + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(bridgeToken2), 1e18, 1e17, 2e9, false); + } + + function test_addBridgeToken_ZeroAddress() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(0), 1e18, 1e17, 5e8, false); + } + + function test_addBridgeToken_AlreadyAdded() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(_bridgeToken), 1e18, 1e17, 5e8, false); + } + + function test_addBridgeToken_SecondTokenAdded() public { + MockToken bridgeToken2 = new MockToken("synapse-agEUR", "synapse-agEUR", 18); + + vm.prank(_GOVERNOR); + _agToken.addBridgeToken(address(bridgeToken2), 1e18, 1e17, 5e8, false); + (uint256 limit, uint256 hourlyLimit, uint64 fee, bool allowed, bool paused) = _agToken.bridges( + address(bridgeToken2) + ); + assertEq(limit, 1e18); + assertEq(hourlyLimit, 1e17); + assertEq(fee, 5e8); + assertTrue(allowed); + assertFalse(paused); + assertEq(_agToken.bridgeTokensList(1), address(bridgeToken2)); + assertEq(_agToken.allBridgeTokens()[1], address(bridgeToken2)); + } + + // ================================= RemoveBridgeToken ================================ + + function test_removeBridgeToken_NotGovernor() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernor.selector); + vm.prank(_alice); + _agToken.removeBridgeToken(address(_bridgeToken)); + } + + function test_removeBridgeToken_NonNullBalance() public { + _bridgeToken.mint(address(_agToken), 1e18); + vm.expectRevert(AgTokenSideChainMultiBridge.AssetStillControlledInReserves.selector); + vm.prank(_GOVERNOR); + _agToken.removeBridgeToken(address(_bridgeToken)); + } + + function test_removeBridgeToken_NormalOneToken() public { + vm.prank(_GOVERNOR); + _agToken.removeBridgeToken(address(_bridgeToken)); + + (uint256 limit, uint256 hourlyLimit, uint64 fee, bool allowed, bool paused) = _agToken.bridges( + address(_bridgeToken) + ); + assertEq(limit, 0); + assertEq(hourlyLimit, 0); + assertEq(fee, 0); + assertFalse(allowed); + assertFalse(paused); + } + + function test_removeBridgeToken_TwoTokensAndFirstIsRemoved() public { + MockToken bridgeToken2 = new MockToken("synapse-agEUR", "synapse-agEUR", 18); + vm.startPrank(_GOVERNOR); + _agToken.addBridgeToken(address(bridgeToken2), 100e18, 10e18, 3e7, true); + _agToken.removeBridgeToken(address(bridgeToken2)); + vm.stopPrank(); + + (uint256 limit, uint256 hourlyLimit, uint64 fee, bool allowed, bool paused) = _agToken.bridges( + address(bridgeToken2) + ); + assertEq(limit, 0); + assertEq(hourlyLimit, 0); + assertEq(fee, 0); + assertFalse(allowed); + assertFalse(paused); + assertEq(_agToken.bridgeTokensList(0), address(_bridgeToken)); + assertEq(_agToken.allBridgeTokens()[0], address(_bridgeToken)); + } + + // ================================= RecoverERC20 ================================ + + function test_recoverERC20_NotGovernor() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernor.selector); + vm.prank(_alice); + _agToken.recoverERC20(address(_bridgeToken), _bob, 1e18); + } + + function test_recoverERC20_InvalidBalance() public { + vm.expectRevert(); + vm.prank(_GOVERNOR); + _agToken.recoverERC20(address(_bridgeToken), _bob, 1e19); + } + + function test_recoverERC20_Normal() public { + _bridgeToken.mint(address(_agToken), 1e18); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 1e18); + vm.prank(_GOVERNOR); + _agToken.recoverERC20(address(_bridgeToken), _bob, 1e18); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 0); + } + + // ================================= SetLimit ================================ + + function test_setLimit_NotGovernorOrGuardian() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernorOrGuardian.selector); + vm.prank(_alice); + _agToken.setLimit(address(_bridgeToken), 1e18); + } + + function test_setLimit_InvalidToken() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + vm.prank(_GOVERNOR); + _agToken.setLimit(_alice, 1e18); + } + + function test_setLimit_Normal() public { + vm.prank(_GOVERNOR); + _agToken.setLimit(address(_bridgeToken), 1000e18); + (uint256 limit, , , , ) = _agToken.bridges(address(_bridgeToken)); + assertEq(limit, 1000e18); + } + + // ================================= SetHourlyLimit ================================ + + function test_setHourlyLimit_NotGovernorOrGuardian() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernorOrGuardian.selector); + vm.prank(_alice); + _agToken.setHourlyLimit(address(_bridgeToken), 1e18); + } + + function test_setHourlyLimit_InvalidToken() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + vm.prank(_GOVERNOR); + _agToken.setHourlyLimit(_alice, 1e18); + } + + function test_setHourlyLimit_Normal() public { + vm.prank(_GOVERNOR); + _agToken.setHourlyLimit(address(_bridgeToken), 1000e18); + (, uint256 hourlyLimit, , , ) = _agToken.bridges(address(_bridgeToken)); + assertEq(hourlyLimit, 1000e18); + } + + // ================================= SetChainTotalHourlyLimit ================================ + + function test_setChainTotalHourlyLimit_NotGovernorOrGuardian() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernorOrGuardian.selector); + vm.prank(_alice); + _agToken.setChainTotalHourlyLimit(1e18); + } + + function test_setChainTotalHourlyLimit_Normal() public { + vm.prank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(1000e18); + assertEq(_agToken.chainTotalHourlyLimit(), 1000e18); + } + + // ================================= SetSwapFee ================================ + + function test_setSwapFee_NotGovernorOrGuardian() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernorOrGuardian.selector); + vm.prank(_alice); + _agToken.setSwapFee(address(_bridgeToken), 5e8); + } + + function test_setSwapFee_InvalidToken() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + vm.prank(_GOVERNOR); + _agToken.setSwapFee(_alice, 5e8); + } + + function test_setSwapFee_TooHighParameterValue() public { + vm.expectRevert(AgTokenSideChainMultiBridge.TooHighParameterValue.selector); + vm.prank(_GOVERNOR); + _agToken.setSwapFee(address(_bridgeToken), 2e9); + } + + function test_setSwapFee_Normal() public { + vm.prank(_GOVERNOR); + _agToken.setSwapFee(address(_bridgeToken), 3e8); + (, , uint64 fee, , ) = _agToken.bridges(address(_bridgeToken)); + assertEq(fee, 3e8); + } + + // ================================= ToggleBridge ================================ + + function test_toggleBridge_NotGovernorOrGuardian() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernorOrGuardian.selector); + vm.prank(_alice); + _agToken.toggleBridge(address(_bridgeToken)); + } + + function test_toggleBridge_NonExistingBridge() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + vm.prank(_GOVERNOR); + _agToken.toggleBridge(_alice); + } + + function test_toggleBridge_Paused() public { + vm.prank(_GOVERNOR); + _agToken.toggleBridge(address(_bridgeToken)); + (, , , , bool paused) = _agToken.bridges(address(_bridgeToken)); + assertTrue(paused); + } + + function test_toggleBridge_Unpaused() public { + vm.startPrank(_GOVERNOR); + _agToken.toggleBridge(address(_bridgeToken)); + _agToken.toggleBridge(address(_bridgeToken)); + vm.stopPrank(); + (, , , , bool paused) = _agToken.bridges(address(_bridgeToken)); + assertFalse(paused); + } + + // ================================= ToggleFeesForAddress ================================ + + function test_toggleFeesForAddress_NotGovernorOrGuardian() public { + vm.expectRevert(AgTokenSideChainMultiBridge.NotGovernorOrGuardian.selector); + vm.prank(_alice); + _agToken.toggleFeesForAddress(_alice); + } + + function test_toggleFeesForAddress_AddressExempted() public { + vm.prank(_GOVERNOR); + _agToken.toggleFeesForAddress(_alice); + assertEq(_agToken.isFeeExempt(_alice), 1); + } + + function test_toggleFeesForAddress_AddressNotExempted() public { + vm.startPrank(_GOVERNOR); + _agToken.toggleFeesForAddress(_alice); + _agToken.toggleFeesForAddress(_alice); + vm.stopPrank(); + assertEq(_agToken.isFeeExempt(_alice), 0); + } + + // ================================= SwapIn ================================ + + function test_swapIn_IncorrectBridgeToken() public { + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + _agToken.swapIn(_bob, 1e18, _alice); + } + + function test_swapIn_PausedBridge() public { + vm.startPrank(_GOVERNOR); + _agToken.toggleBridge(address(_bridgeToken)); + vm.stopPrank(); + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + _agToken.swapIn(address(_bridgeToken), 1e18, _alice); + } + + function test_swapIn_InsufficentBalanceOrApproval() public { + vm.startPrank(_GOVERNOR); + _agToken.setLimit(address(_bridgeToken), 100e18); + _agToken.setHourlyLimit(address(_bridgeToken), 100e18); + _bridgeToken.mint(_GOVERNOR, 10e18); + vm.expectRevert(); + _agToken.swapIn(address(_bridgeToken), 50e18, _alice); + _bridgeToken.approve(address(_agToken), 100e18); + vm.expectRevert(); + _agToken.swapIn(address(_bridgeToken), 50e18, _alice); + vm.stopPrank(); + } + + function test_swapIn_ZeroLimitSwaps() public { + vm.startPrank(_GOVERNOR); + _agToken.setLimit(address(_bridgeToken), 0); + _agToken.swapIn(address(_bridgeToken), 1e18, _alice); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_bob), 0); + } + + function test_swapIn_AmountGreaterThanLimit() public { + vm.startPrank(_GOVERNOR); + _agToken.setLimit(address(_bridgeToken), 10e18); + _agToken.setHourlyLimit(address(_bridgeToken), 10e18); + _agToken.setSwapFee(address(_bridgeToken), 0); + _bridgeToken.mint(_GOVERNOR, 100e18); + _bridgeToken.approve(address(_agToken), 100e18); + _agToken.swapIn(address(_bridgeToken), 100e18, _bob); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_bob), 10e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 90e18); + assertEq(_agToken.currentUsage(address(_bridgeToken)), 10e18); + } + + function test_swapIn_AmountGreaterThanHourlyLimit() public { + vm.startPrank(_GOVERNOR); + _agToken.setLimit(address(_bridgeToken), 10e18); + _agToken.setHourlyLimit(address(_bridgeToken), 1e18); + _agToken.setSwapFee(address(_bridgeToken), 0); + _bridgeToken.mint(_GOVERNOR, 2e18); + _bridgeToken.approve(address(_agToken), 2e18); + assertEq(_agToken.balanceOf(_bob), 0); + _agToken.swapIn(address(_bridgeToken), 2e18, _bob); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_bob), 1e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 1e18); + assertEq(_agToken.currentUsage(address(_bridgeToken)), 1e18); + } + + function test_swapIn_TotalAmountGreaterThanHourlyLimit() public { + vm.startPrank(_GOVERNOR); + _agToken.setLimit(address(_bridgeToken), 10e18); + _agToken.setHourlyLimit(address(_bridgeToken), 2e18); + _bridgeToken.mint(_GOVERNOR, 3e18); + _bridgeToken.approve(address(_agToken), 3e18); + _agToken.swapIn(address(_bridgeToken), 1e18, _alice); + assertEq(_agToken.currentUsage(address(_bridgeToken)), 1e18); + _agToken.swapIn(address(_bridgeToken), 2e18, _alice); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_alice), 2e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 1e18); + assertEq(_agToken.currentUsage(address(_bridgeToken)), 2e18); + } + + function test_swapIn_HourlyLimitOverTwoHours() public { + vm.startPrank(_GOVERNOR); + _agToken.setSwapFee(address(_bridgeToken), 0); + _agToken.setLimit(address(_bridgeToken), 10e18); + _agToken.setHourlyLimit(address(_bridgeToken), 2e18); + _bridgeToken.mint(_GOVERNOR, 3e18); + _bridgeToken.approve(address(_agToken), 3e18); + _agToken.swapIn(address(_bridgeToken), 1e18, _bob); + assertEq(_agToken.currentUsage(address(_bridgeToken)), 1e18); + assertEq(_agToken.balanceOf(_bob), 1e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 2e18); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 1e18); + + uint256 hour = block.timestamp / 3600; + assertEq(_agToken.usage(address(_bridgeToken), hour), 1e18); + vm.warp(block.timestamp + 3600); + hour = block.timestamp / 3600; + assertEq(_agToken.usage(address(_bridgeToken), hour - 1), 1e18); + assertEq(_agToken.usage(address(_bridgeToken), hour), 0); + assertEq(_agToken.currentUsage(address(_bridgeToken)), 0); + _agToken.swapIn(address(_bridgeToken), 2e18, _bob); + assertEq(_agToken.usage(address(_bridgeToken), hour), 2e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 0); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 3e18); + assertEq(_agToken.balanceOf(_bob), 3e18); + } + + function test_swapIn_WithSomeTransactionFees() public { + vm.startPrank(_GOVERNOR); + _agToken.setSwapFee(address(_bridgeToken), 5e8); + _agToken.setLimit(address(_bridgeToken), 100e18); + _agToken.setHourlyLimit(address(_bridgeToken), 10e18); + _bridgeToken.mint(_GOVERNOR, 10e18); + _bridgeToken.approve(address(_agToken), 10e18); + _agToken.swapIn(address(_bridgeToken), 10e18, _bob); + vm.stopPrank(); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 10e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 0e18); + assertEq(_agToken.balanceOf(_bob), 5e18); + } + + function test_swapIn_WithSomeTransactionFesAndExempt() public { + vm.startPrank(_GOVERNOR); + _agToken.toggleFeesForAddress(_GOVERNOR); + _agToken.setSwapFee(address(_bridgeToken), 5e8); + _agToken.setLimit(address(_bridgeToken), 100e18); + _agToken.setHourlyLimit(address(_bridgeToken), 100e18); + _bridgeToken.mint(_GOVERNOR, 10e18); + _bridgeToken.approve(address(_agToken), 10e18); + _agToken.swapIn(address(_bridgeToken), 10e18, _bob); + vm.stopPrank(); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 10e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 0e18); + assertEq(_agToken.balanceOf(_bob), 10e18); + } + + function test_swapIn_WithoutTransactionsFeesAndExempt() public { + vm.startPrank(_GOVERNOR); + _agToken.setSwapFee(address(_bridgeToken), 0); + _agToken.setLimit(address(_bridgeToken), 100e18); + _agToken.setHourlyLimit(address(_bridgeToken), 100e18); + _bridgeToken.mint(_GOVERNOR, 10e18); + _bridgeToken.approve(address(_agToken), 10e18); + _agToken.swapIn(address(_bridgeToken), 10e18, _bob); + vm.stopPrank(); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 10e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 0e18); + assertEq(_agToken.balanceOf(_bob), 10e18); + } + + function test_swapIn_WithWeirdTransactionFees() public { + vm.startPrank(_GOVERNOR); + _agToken.setSwapFee(address(_bridgeToken), 4e5); + _agToken.setLimit(address(_bridgeToken), 100e18); + _agToken.setHourlyLimit(address(_bridgeToken), 100e18); + _bridgeToken.mint(_GOVERNOR, 100e18); + _bridgeToken.approve(address(_agToken), 100e18); + _agToken.swapIn(address(_bridgeToken), 100e18, _bob); + vm.stopPrank(); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 100e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 0e18); + assertEq(_agToken.balanceOf(_bob), 9996e16); + } + + // ================================= SwapOut ================================ + + function test_swapOut_IncorrectBridgeToken() public { + vm.prank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(type(uint256).max); + + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + _agToken.swapOut(_bob, 1e18, _alice); + } + + function test_swapOut_BridgeTokenPaused() public { + vm.prank(_GOVERNOR); + _agToken.toggleBridge(address(_bridgeToken)); + + vm.prank(_GOVERNOR); + vm.expectRevert(AgTokenSideChainMultiBridge.InvalidToken.selector); + _agToken.swapOut(address(_bridgeToken), 1e18, _alice); + } + + function test_swapOut_InvalidBridgeTokenBalance() public { + vm.startPrank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(type(uint256).max); + _agToken.setSwapFee(address(_bridgeToken), 5e8); + _agToken.mint(_GOVERNOR, 100e18); + vm.expectRevert(); + _agToken.swapOut(address(_bridgeToken), 1e18, _alice); + vm.stopPrank(); + } + + function test_swapOut_HourlyLimitExceeded() public { + uint256 limit = 10e18; + vm.startPrank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(limit); + + _agToken.mint(_GOVERNOR, 100e18); + _bridgeToken.mint(address(_agToken), 100e18); + _agToken.swapOut(address(_bridgeToken), 9e18, _bob); + vm.expectRevert(AgTokenSideChainMultiBridge.HourlyLimitExceeded.selector); + _agToken.swapOut(address(_bridgeToken), 2e18, _bob); + vm.stopPrank(); + } + + function test_swapOut_HourlyLimitExceededAtDifferentHours() public { + uint256 limit = 10e18; + vm.startPrank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(limit); + + _agToken.mint(_GOVERNOR, 100e18); + _bridgeToken.mint(address(_agToken), 100e18); + + _agToken.swapOut(address(_bridgeToken), 9e18, _bob); + vm.warp(block.timestamp + 3600); + _agToken.swapOut(address(_bridgeToken), 2e18, _bob); + vm.expectRevert(AgTokenSideChainMultiBridge.HourlyLimitExceeded.selector); + _agToken.swapOut(address(_bridgeToken), 81e17, _bob); + vm.stopPrank(); + } + + function test_swapOut_WithValidBridgeTokenBalance() public { + vm.startPrank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(type(uint256).max); + _agToken.setSwapFee(address(_bridgeToken), 5e8); + _agToken.mint(_GOVERNOR, 100e18); + _bridgeToken.mint(address(_agToken), 100e18); + _agToken.swapOut(address(_bridgeToken), 100e18, _bob); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_GOVERNOR), 0); + assertEq(_bridgeToken.balanceOf(_bob), 50e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 0); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 50e18); + } + + function test_swapOut_WithValidBridgeTokenBalanceButFeeExemption() public { + vm.startPrank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(type(uint256).max); + _agToken.toggleFeesForAddress(_GOVERNOR); + _agToken.setSwapFee(address(_bridgeToken), 5e8); + _agToken.mint(_GOVERNOR, 100e18); + _bridgeToken.mint(address(_agToken), 100e18); + _agToken.swapOut(address(_bridgeToken), 100e18, _bob); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_GOVERNOR), 0); + assertEq(_bridgeToken.balanceOf(_bob), 100e18); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 0); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 0); + } + + function test_swapOut_WithWeirdTransactionFees() public { + vm.startPrank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(type(uint256).max); + _agToken.setSwapFee(address(_bridgeToken), 4e5); + _agToken.mint(_GOVERNOR, 100e18); + _bridgeToken.mint(address(_agToken), 100e18); + _agToken.swapOut(address(_bridgeToken), 100e18, _bob); + vm.stopPrank(); + assertEq(_agToken.balanceOf(_GOVERNOR), 0); + assertEq(_bridgeToken.balanceOf(_bob), 9996e16); + assertEq(_bridgeToken.balanceOf(_GOVERNOR), 0); + assertEq(_bridgeToken.balanceOf(address(_agToken)), 4e16); + } + + function test_swapOut_HourlyLimitAtDifferentHours() public { + uint256 limit = 10e18; + vm.startPrank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(limit); + + _agToken.mint(_GOVERNOR, 100e18); + _bridgeToken.mint(address(_agToken), 100e18); + + _agToken.swapOut(address(_bridgeToken), 9e18, _bob); + assertEq(_agToken.chainTotalUsage(0), 9e18); + vm.warp(block.timestamp + 3600); + _agToken.swapOut(address(_bridgeToken), 2e18, _bob); + assertEq(_agToken.chainTotalUsage(1), 2e18); + + _agToken.swapOut(address(_bridgeToken), 8e18, _bob); + assertEq(_agToken.chainTotalUsage(1), 10e18); + vm.stopPrank(); + } + + function test_swapOut_HourlyLimitUpdatedByGovernance() public { + uint256 limit = 10e18; + vm.startPrank(_GOVERNOR); + _agToken.setChainTotalHourlyLimit(limit); + + _agToken.mint(_GOVERNOR, 100e18); + _bridgeToken.mint(address(_agToken), 100e18); + + _agToken.swapOut(address(_bridgeToken), 9e18, _bob); + vm.expectRevert(AgTokenSideChainMultiBridge.HourlyLimitExceeded.selector); + _agToken.swapOut(address(_bridgeToken), 2e18, _bob); + + uint256 hour = block.timestamp / 3600; + assertEq(_agToken.chainTotalUsage(hour), 9e18); + + _agToken.setChainTotalHourlyLimit(11e18); + _agToken.swapOut(address(_bridgeToken), 2e18, _bob); + assertEq(_agToken.chainTotalUsage(hour), 11e18); + + vm.expectRevert(AgTokenSideChainMultiBridge.HourlyLimitExceeded.selector); + _agToken.swapOut(address(_bridgeToken), 1e17, _bob); + + assertEq(_agToken.chainTotalUsage(hour), 11e18); + vm.stopPrank(); + } +} diff --git a/test/units/coreBorrow/CoreBorrow.t.sol b/test/units/coreBorrow/CoreBorrow.t.sol new file mode 100644 index 0000000..e5d3e44 --- /dev/null +++ b/test/units/coreBorrow/CoreBorrow.t.sol @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "../BaseTest.t.sol"; +import { CoreBorrow, ICoreBorrow } from "contracts/coreBorrow/CoreBorrow.sol"; +import { MockTreasury } from "contracts/mock/MockTreasury.sol"; +import { IAgToken } from "contracts/interfaces/IAgToken.sol"; +import { MockFlashLoanModule } from "contracts/mock/MockFlashLoanModule.sol"; + +contract CoreBorrowTest is BaseTest { + MockTreasury internal _treasury; + CoreBorrow internal _coreBorrowImplem; + CoreBorrow internal _coreBorrow; + MockFlashLoanModule internal _flashAngle; + + bytes32 constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); + bytes32 constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); + bytes32 constant FLASHLOANER_TREASURY_ROLE = keccak256("FLASHLOANER_TREASURY_ROLE"); + + function setUp() public override { + super.setUp(); + + _coreBorrowImplem = new CoreBorrow(); + _coreBorrow = CoreBorrow(_deployUpgradeable(address(proxyAdmin), address(_coreBorrowImplem), "")); + + _treasury = new MockTreasury(IAgToken(address(0)), address(0), address(0), address(0), address(0), address(0)); + _flashAngle = new MockFlashLoanModule(_coreBorrow); + + _coreBorrow.initialize(_GOVERNOR, _GUARDIAN); + } + + // ================================= INITIALIZE ================================ + + function test_initialize_Constructor() public { + assertEq(_coreBorrow.isGovernor(_GOVERNOR), true); + assertEq(_coreBorrow.isGovernor(_GUARDIAN), false); + assertEq(_coreBorrow.isGovernorOrGuardian(_GUARDIAN), true); + assertEq(_coreBorrow.isGovernorOrGuardian(_GOVERNOR), true); + assertEq(_coreBorrow.isFlashLoanerTreasury(_GOVERNOR), false); + assertEq(_coreBorrow.isFlashLoanerTreasury(_GUARDIAN), false); + assertEq(_coreBorrow.getRoleAdmin(GUARDIAN_ROLE), GOVERNOR_ROLE); + assertEq(_coreBorrow.getRoleAdmin(GOVERNOR_ROLE), GOVERNOR_ROLE); + assertEq(_coreBorrow.getRoleAdmin(FLASHLOANER_TREASURY_ROLE), GOVERNOR_ROLE); + assertEq(_coreBorrow.hasRole(GOVERNOR_ROLE, _GOVERNOR), true); + assertEq(_coreBorrow.hasRole(GUARDIAN_ROLE, _GUARDIAN), true); + assertEq(_coreBorrow.hasRole(GUARDIAN_ROLE, _GOVERNOR), true); + assertEq(_coreBorrow.hasRole(FLASHLOANER_TREASURY_ROLE, _GOVERNOR), false); + assertEq(_coreBorrow.flashLoanModule(), address(0)); + } + + function test_initialize_AlreadyInitialized() public { + vm.expectRevert("Initializable: contract is already initialized"); + _coreBorrow.initialize(_GOVERNOR, _GUARDIAN); + } + + function test_initialize_InvalidGovernorGuardian() public { + _coreBorrow = CoreBorrow(_deployUpgradeable(address(proxyAdmin), address(_coreBorrowImplem), "")); + vm.expectRevert(CoreBorrow.IncompatibleGovernorAndGuardian.selector); + _coreBorrow.initialize(_GOVERNOR, _GOVERNOR); + + vm.expectRevert(CoreBorrow.ZeroAddress.selector); + _coreBorrow.initialize(address(0), _GUARDIAN); + + vm.expectRevert(CoreBorrow.ZeroAddress.selector); + _coreBorrow.initialize(_GOVERNOR, address(0)); + } + + // ================================= AddGovernor ================================ + + function test_addGovernor_NotGovernor() public { + vm.expectRevert(); + vm.prank(_alice); + _coreBorrow.addGovernor(_alice); + } + + function test_addGovernor_Normal() public { + vm.prank(_GOVERNOR); + _coreBorrow.addGovernor(_alice); + assertEq(_coreBorrow.isGovernor(_alice), true); + assertEq(_coreBorrow.isGovernorOrGuardian(_alice), true); + assertEq(_coreBorrow.hasRole(GOVERNOR_ROLE, _alice), true); + assertEq(_coreBorrow.hasRole(GUARDIAN_ROLE, _alice), true); + } + + function test_addGovernor_Multi() public { + vm.prank(_GOVERNOR); + _coreBorrow.addGovernor(_alice); + vm.prank(_alice); + _coreBorrow.addGovernor(_bob); + assertEq(_coreBorrow.isGovernor(_bob), true); + assertEq(_coreBorrow.isGovernorOrGuardian(_bob), true); + assertEq(_coreBorrow.hasRole(GOVERNOR_ROLE, _bob), true); + assertEq(_coreBorrow.hasRole(GUARDIAN_ROLE, _bob), true); + } + + // ================================= RemoveGovernor ================================ + + function test_removeGovernor_NotGovernor() public { + vm.expectRevert(); + vm.prank(_alice); + _coreBorrow.removeGovernor(_alice); + } + + function test_removeGovernor_NotEnoughGovernorsLeft() public { + vm.expectRevert(CoreBorrow.NotEnoughGovernorsLeft.selector); + _coreBorrow.removeGovernor(_GOVERNOR); + } + + function test_removeGovernor_NormalAfterAsk() public { + vm.prank(_GOVERNOR); + _coreBorrow.addGovernor(_alice); + vm.prank(_alice); + _coreBorrow.removeGovernor(_alice); + + assertEq(_coreBorrow.isGovernor(_alice), false); + assertEq(_coreBorrow.isGovernorOrGuardian(_alice), false); + assertEq(_coreBorrow.hasRole(GOVERNOR_ROLE, _alice), false); + assertEq(_coreBorrow.hasRole(GUARDIAN_ROLE, _alice), false); + } + + function test_removeGovernor_Normal() public { + vm.prank(_GOVERNOR); + _coreBorrow.addGovernor(_alice); + vm.prank(_GOVERNOR); + _coreBorrow.removeGovernor(_alice); + + assertEq(_coreBorrow.isGovernor(_alice), false); + assertEq(_coreBorrow.isGovernorOrGuardian(_alice), false); + assertEq(_coreBorrow.hasRole(GOVERNOR_ROLE, _alice), false); + assertEq(_coreBorrow.hasRole(GUARDIAN_ROLE, _alice), false); + } + + // ================================= SetFlashLoanModule ================================ + + function test_setFlashLoanModule_NotGovernor() public { + vm.expectRevert(); + vm.prank(_alice); + _coreBorrow.setFlashLoanModule(_GOVERNOR); + } + + function test_setFlashLoanModule_ZeroAddress() public { + vm.prank(_GOVERNOR); + _coreBorrow.setFlashLoanModule(address(0)); + assertEq(_coreBorrow.flashLoanModule(), address(0)); + } + + function test_setFlashLoanModule_NoTreasury() public { + vm.prank(_GOVERNOR); + _coreBorrow.setFlashLoanModule(address(_flashAngle)); + assertEq(_coreBorrow.flashLoanModule(), address(_flashAngle)); + } + + function test_setFlashLoanModule_WrongCore() public { + _flashAngle = new MockFlashLoanModule(CoreBorrow(_GOVERNOR)); + vm.expectRevert(CoreBorrow.InvalidCore.selector); + vm.prank(_GOVERNOR); + _coreBorrow.setFlashLoanModule(address(_flashAngle)); + } + + function test_setFlashLoanModule_Normal() public { + vm.startPrank(_GOVERNOR); + _coreBorrow.addFlashLoanerTreasuryRole(address(_treasury)); + _coreBorrow.setFlashLoanModule(address(_flashAngle)); + vm.stopPrank(); + assertEq(_coreBorrow.flashLoanModule(), address(_flashAngle)); + assertEq(_treasury.flashLoanModule(), address(_flashAngle)); + } + + // ================================= AddFlashLoanerTreasuryRole ================================ + + function test_addFlashLoanerTreasuryRole_NotGovernor() public { + vm.expectRevert(); + vm.prank(_alice); + _coreBorrow.addFlashLoanerTreasuryRole(address(_treasury)); + } + + function test_addFlashLoanerTreasuryRole_ZeroFlashLoandModule() public { + vm.prank(_GOVERNOR); + _coreBorrow.addFlashLoanerTreasuryRole(address(_treasury)); + assertEq(_coreBorrow.isFlashLoanerTreasury(address(_treasury)), true); + assertEq(_coreBorrow.hasRole(FLASHLOANER_TREASURY_ROLE, address(_treasury)), true); + } + + function test_addFlashLoanerTreasuryRole_Normal() public { + vm.startPrank(_GOVERNOR); + _coreBorrow.setFlashLoanModule(address(_flashAngle)); + _coreBorrow.addFlashLoanerTreasuryRole(address(_treasury)); + vm.stopPrank(); + assertEq(_coreBorrow.isFlashLoanerTreasury(address(_treasury)), true); + assertEq(_coreBorrow.hasRole(FLASHLOANER_TREASURY_ROLE, address(_treasury)), true); + assertEq(_coreBorrow.flashLoanModule(), address(_flashAngle)); + assertEq(_treasury.flashLoanModule(), address(_flashAngle)); + assertEq(_flashAngle.stablecoinsSupported(address(_treasury)), true); + } + + // ================================= RemoveFlashLoanerTreasuryRole ================================ + + function test_removeFlashLoanerTreasuryRole_NotGovernor() public { + vm.expectRevert(); + vm.prank(_alice); + _coreBorrow.removeFlashLoanerTreasuryRole(address(_treasury)); + } + + function test_removeFlashLoanerTreasuryRole_ZeroFlashLoanModule() public { + vm.startPrank(_GOVERNOR); + _coreBorrow.addFlashLoanerTreasuryRole(address(_treasury)); + _coreBorrow.removeFlashLoanerTreasuryRole(address(_treasury)); + vm.stopPrank(); + assertEq(_coreBorrow.isFlashLoanerTreasury(address(_treasury)), false); + assertEq(_coreBorrow.hasRole(FLASHLOANER_TREASURY_ROLE, address(_treasury)), false); + } + + function test_removeFlashLoanerTreasuryRole_Normal() public { + vm.startPrank(_GOVERNOR); + _coreBorrow.setFlashLoanModule(address(_flashAngle)); + _coreBorrow.addFlashLoanerTreasuryRole(address(_treasury)); + _coreBorrow.removeFlashLoanerTreasuryRole(address(_treasury)); + vm.stopPrank(); + assertEq(_coreBorrow.isFlashLoanerTreasury(address(_treasury)), false); + assertEq(_coreBorrow.hasRole(FLASHLOANER_TREASURY_ROLE, address(_treasury)), false); + assertEq(_coreBorrow.flashLoanModule(), address(_flashAngle)); + assertEq(_treasury.flashLoanModule(), address(0)); + assertEq(_flashAngle.stablecoinsSupported(address(_treasury)), false); + } + + // ================================= SetCore ================================ + + function test_setCore_NotGovernor() public { + vm.expectRevert(); + vm.prank(_alice); + _coreBorrow.setCore(ICoreBorrow(address(_treasury))); + } + + function test_setCore_GoodGovernorNoFlashLoanModule() public { + CoreBorrow coreBorrowRevert = CoreBorrow( + _deployUpgradeable(address(proxyAdmin), address(_coreBorrowImplem), "") + ); + coreBorrowRevert.initialize(_GOVERNOR, _GUARDIAN); + + vm.expectEmit(true, true, true, true); + emit CoreBorrow.CoreUpdated(address(coreBorrowRevert)); + vm.prank(_GOVERNOR); + _coreBorrow.setCore(ICoreBorrow(address(coreBorrowRevert))); + } + + function test_setCore_GoodGovernorAndFlashLoanModule() public { + CoreBorrow coreBorrowRevert = CoreBorrow( + _deployUpgradeable(address(proxyAdmin), address(_coreBorrowImplem), "") + ); + coreBorrowRevert.initialize(_GOVERNOR, _GUARDIAN); + + vm.startPrank(_GOVERNOR); + _coreBorrow.setFlashLoanModule(address(_flashAngle)); + vm.expectEmit(true, true, true, true); + emit CoreBorrow.CoreUpdated(address(coreBorrowRevert)); + _coreBorrow.setCore(ICoreBorrow(address(coreBorrowRevert))); + vm.stopPrank(); + assertEq(address(_flashAngle.core()), address(coreBorrowRevert)); + } + + function test_setCore_WrongGovernor() public { + CoreBorrow coreBorrowRevert = CoreBorrow( + _deployUpgradeable(address(proxyAdmin), address(_coreBorrowImplem), "") + ); + coreBorrowRevert.initialize(_GOVERNOR_POLYGON, _alice); + + vm.expectRevert(CoreBorrow.InvalidCore.selector); + vm.prank(_GOVERNOR); + _coreBorrow.setCore(ICoreBorrow(address(coreBorrowRevert))); + } + + // ================================= GrantGuardianRole ================================ + + function test_grantGuardianRole_NotGuardian() public { + vm.expectRevert(); + vm.prank(_alice); + _coreBorrow.grantRole(GUARDIAN_ROLE, _alice); + } + + function test_grantGuardianRole_Normal() public { + vm.prank(_GOVERNOR); + _coreBorrow.grantRole(GUARDIAN_ROLE, _alice); + assertEq(_coreBorrow.isGovernorOrGuardian(_alice), true); + assertEq(_coreBorrow.hasRole(GUARDIAN_ROLE, _alice), true); + } + + // ================================= RevokeGuardianRole ================================ + + function test_revokeGuardianRole_NotGuardian() public { + vm.expectRevert(); + vm.prank(_alice); + _coreBorrow.revokeRole(GUARDIAN_ROLE, _alice); + } + + function test_revokeGuardianRole_Normal() public { + vm.startPrank(_GOVERNOR); + _coreBorrow.grantRole(GUARDIAN_ROLE, _alice); + _coreBorrow.revokeRole(GUARDIAN_ROLE, _alice); + vm.stopPrank(); + assertEq(_coreBorrow.isGovernorOrGuardian(_alice), false); + assertEq(_coreBorrow.hasRole(GUARDIAN_ROLE, _alice), false); + } +} diff --git a/test/units/flashAngle/FlashAngle.t.sol b/test/units/flashAngle/FlashAngle.t.sol new file mode 100644 index 0000000..1f53d19 --- /dev/null +++ b/test/units/flashAngle/FlashAngle.t.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { stdStorage, StdStorage } from "forge-std/Test.sol"; +import "../BaseTest.t.sol"; +import { MockToken } from "contracts/mock/MockToken.sol"; +import { MockFlashLoanReceiver } from "contracts/mock/MockFlashLoanReceiver.sol"; +import { MockCoreBorrow, ICoreBorrow } from "contracts/mock/MockCoreBorrow.sol"; +import { FlashAngle, IFlashAngle, IERC3156FlashBorrower } from "contracts/flashloan/FlashAngle.sol"; +import { MockTreasury } from "contracts/mock/MockTreasury.sol"; +import { IAgToken } from "contracts/interfaces/IAgToken.sol"; + +contract FlashAngleTest is BaseTest { + using stdStorage for StdStorage; + + MockToken internal _token; + MockTreasury internal _treasury; + MockFlashLoanReceiver internal _flashLoanReceiver; + MockCoreBorrow internal _coreBorrow; + FlashAngle internal _flashAngleImplem; + FlashAngle internal _flashAngle; + + function setUp() public override { + super.setUp(); + + _token = new MockToken("agEUR", "agEUR", 18); + + _coreBorrow = new MockCoreBorrow(); + _flashLoanReceiver = new MockFlashLoanReceiver(); + + _treasury = new MockTreasury( + IAgToken(address(_token)), + address(0), + address(0), + address(0), + address(0), + address(0) + ); + + _flashAngleImplem = new FlashAngle(); + _flashAngle = FlashAngle(_deployUpgradeable(address(proxyAdmin), address(_flashAngleImplem), "")); + _flashAngle.initialize(ICoreBorrow(address(_coreBorrow))); + + _coreBorrow.addStablecoinSupport(IFlashAngle(address(_flashAngle)), address(_treasury)); + _coreBorrow.toggleGovernor(_GOVERNOR); + _coreBorrow.toggleGuardian(_GUARDIAN); + } + + // ================================= INITIALIZE ================================ + + function test_initialize_Constructor() public { + assertEq(address(_flashAngle.core()), address(_coreBorrow)); + assertEq(address(_treasury.stablecoin()), address(_token)); + (uint256 maxBorrowable, uint64 flashLoanFee, address treasury) = _flashAngle.stablecoinMap( + IAgToken(address(_token)) + ); + assertEq(treasury, address(_treasury)); + assertEq(flashLoanFee, 0); + assertEq(maxBorrowable, 0); + } + + function test_initialize_AlreadyInitiliazed() public { + vm.expectRevert("Initializable: contract is already initialized"); + _flashAngle.initialize(ICoreBorrow(_alice)); + } + + function test_initialize_ZeroAddress() public { + vm.expectRevert("Initializable: contract is already initialized"); + _flashAngle.initialize(ICoreBorrow(address(0))); + } + + // ================================= AddStableCoinSupport ================================ + + function test_addStablecoinSupport_NotCore() public { + vm.expectRevert(FlashAngle.NotCore.selector); + _flashAngle.addStablecoinSupport(_GUARDIAN); + } + + function test_addStablecoinSupport_Normal() public { + _treasury = new MockTreasury( + IAgToken(address(_token)), + address(0), + address(0), + address(0), + address(0), + address(0) + ); + _coreBorrow.addStablecoinSupport(IFlashAngle(address(_flashAngle)), address(_treasury)); + (uint256 maxBorrowable, uint64 flashLoanFee, address treasury) = _flashAngle.stablecoinMap( + IAgToken(address(_token)) + ); + assertEq(treasury, address(_treasury)); + assertEq(flashLoanFee, 0); + assertEq(maxBorrowable, 0); + } + + // ================================= RemoveStableCoinSupport ================================ + + function test_removeStablecoinSupport_NotCore() public { + vm.expectRevert(FlashAngle.NotCore.selector); + _flashAngle.removeStablecoinSupport(address(_treasury)); + } + + function test_removeStablecoinSupport_Normal() public { + _coreBorrow.removeStablecoinSupport(_flashAngle, address(_treasury)); + (uint256 maxBorrowable, uint64 flashLoanFee, address treasury) = _flashAngle.stablecoinMap( + IAgToken(address(_token)) + ); + assertEq(treasury, address(0)); + assertEq(flashLoanFee, 0); + assertEq(maxBorrowable, 0); + } + + // ================================= SetCore ================================ + + function test_setCore_NotCore() public { + vm.expectRevert(FlashAngle.NotCore.selector); + _flashAngle.setCore(_alice); + } + + function test_setCore_Normal() public { + _coreBorrow.setCore(_flashAngle, _alice); + assertEq(address(_flashAngle.core()), _alice); + } + + // ================================= SetFlashLoanParameters ================================ + + function test_setFlashLoanParameters_UnsupportedStablecoin() public { + vm.expectRevert(FlashAngle.UnsupportedStablecoin.selector); + _flashAngle.setFlashLoanParameters(IAgToken(address(0)), 1e18, 0); + } + + function test_setFlashLoanParameters_NotGovernorOrGuardian() public { + vm.expectRevert(FlashAngle.NotGovernorOrGuardian.selector); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 1e18, 0); + } + + function test_setFlashLoanParameters_TooHighFee() public { + vm.expectRevert(FlashAngle.TooHighParameterValue.selector); + vm.prank(_GOVERNOR); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 1e18, 0); + } + + function test_setFlashLoanParameters_Normal() public { + vm.prank(_GOVERNOR); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 5e8, 100e18); + (uint256 maxBorrowable, uint64 flashLoanFee, address treasury) = _flashAngle.stablecoinMap( + IAgToken(address(_token)) + ); + assertEq(treasury, address(_treasury)); + assertEq(flashLoanFee, 5e8); + assertEq(maxBorrowable, 100e18); + } + + // ================================= FlashFee ================================ + + function test_flashFee_UnsupportedStablecoin() public { + vm.expectRevert(FlashAngle.UnsupportedStablecoin.selector); + _flashAngle.flashFee(address(0), 1e18); + } + + function test_flashFee_NormalNoFlashFee() public { + assertEq(_flashAngle.flashFee(address(_token), 1e18), 0); + } + + function test_flashFee_NormalWithFlashFee() public { + vm.prank(_GOVERNOR); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 5e8, 100e18); + assertEq(_flashAngle.flashFee(address(_token), 1e18), 5e17); + } + + // ================================= MaxFlashLoan ================================ + + function test_maxFlashLoan_NonExistingToken() public { + assertEq(_flashAngle.maxFlashLoan(_GUARDIAN), 0); + } + + function test_maxFlashLoan_UninitalizedParameters() public { + assertEq(_flashAngle.maxFlashLoan(address(_token)), 0); + } + + function test_maxFlashLoan_Normal() public { + vm.prank(_GOVERNOR); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 5e8, 100e18); + assertEq(_flashAngle.maxFlashLoan(address(_token)), 100e18); + } + + // ================================= AccruteInterestToTreasury ================================ + + function test_accrueInterestToTreasury_NotTreasury() public { + vm.expectRevert(FlashAngle.NotTreasury.selector); + _flashAngle.accrueInterestToTreasury(IAgToken(address(_token))); + } + + function test_accureInterestToTreasury_InvalidStablecoin() public { + vm.expectRevert(FlashAngle.NotTreasury.selector); + _flashAngle.accrueInterestToTreasury(IAgToken(address(_token))); + } + + function test_accrueInterestToTreasury_NormalZeroBalance() public { + _treasury.accrueInterestToTreasury(_flashAngle); + assertEq(_token.balanceOf(address(_flashAngle)), 0); + } + + function test_accrueInterestToTreasury_Normal() public { + _token.mint(address(_flashAngle), 1e18); + _treasury.accrueInterestToTreasury(_flashAngle); + assertEq(_token.balanceOf(address(_flashAngle)), 0); + } + + // ================================= FlashLoan ================================ + + function test_flashLoan_UnsupportedStablecoin() public { + vm.expectRevert(FlashAngle.UnsupportedStablecoin.selector); + _flashAngle.flashLoan(_flashLoanReceiver, _GUARDIAN, 0, ""); + } + + function test_flashloan_TooBigAmount() public { + vm.expectRevert(FlashAngle.TooBigAmount.selector); + _flashAngle.flashLoan(_flashLoanReceiver, address(_token), 1e18, ""); + } + + function test_flashLoan_InvalidReturnMessage() public { + vm.prank(_GOVERNOR); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 5e8, 10000e18); + + vm.expectRevert(FlashAngle.InvalidReturnMessage.selector); + _flashAngle.flashLoan(_flashLoanReceiver, address(_token), 1001e18, ""); + } + + function test_flashloan_TooSmallBalance() public { + vm.prank(_GOVERNOR); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 5e8, 10000e18); + + vm.expectRevert("ERC20: transfer amount exceeds balance"); + _flashAngle.flashLoan(_flashLoanReceiver, address(_token), 100e18, ""); + } + + function test_flashLoan_Normal() public { + vm.prank(_GOVERNOR); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 5e8, 10000e18); + _token.mint(address(_flashLoanReceiver), 50e18); + + assertEq(_token.balanceOf(address(_flashLoanReceiver)), 50e18); + assertEq(_token.balanceOf(address(_flashAngle)), 0); + assertEq(_flashAngle.flashFee(address(_token), 100e18), 50e18); + + vm.expectEmit(true, true, true, true); + emit FlashAngle.FlashLoan(address(_token), 100e18, IERC3156FlashBorrower(address(_flashLoanReceiver))); + _flashAngle.flashLoan(_flashLoanReceiver, address(_token), 100e18, "FlashLoanReceiver"); + + assertEq(_token.balanceOf(address(_flashLoanReceiver)), 0); + assertEq(_token.balanceOf(address(_flashAngle)), 50e18); + } + + function test_flashloan_Reentrant() public { + vm.prank(_GOVERNOR); + _flashAngle.setFlashLoanParameters(IAgToken(address(_token)), 5e8, 10000e18); + _token.mint(address(_flashLoanReceiver), 50e18); + + assertEq(_token.balanceOf(address(_flashLoanReceiver)), 50e18); + assertEq(_token.balanceOf(address(_flashAngle)), 0); + assertEq(_flashAngle.flashFee(address(_token), 100e18), 50e18); + + vm.expectRevert("ReentrancyGuard: reentrant call"); + _flashAngle.flashLoan(_flashLoanReceiver, address(_token), 2e18, ""); + } +} diff --git a/test/units/utils/Solenv.sol b/test/units/utils/Solenv.sol new file mode 100644 index 0000000..3716b32 --- /dev/null +++ b/test/units/utils/Solenv.sol @@ -0,0 +1,809 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import { Vm } from "forge-std/Vm.sol"; + +/* +Copied from "memester-xyz/solenv" +https://github.com/memester-xyz/solenv +*/ + +library Solenv { + using strings for *; + + Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + + function config(string memory filename) internal { + string[] memory inputs = new string[](3); + inputs[0] = "sh"; + inputs[1] = "-c"; + inputs[2] = string( + bytes.concat('cast abi-encode "response(bytes)" $(xxd -p -c 1000000 ', bytes(filename), ")") + ); + + bytes memory res = vm.ffi(inputs); + + strings.slice memory data = abi.decode(res, (string)).toSlice(); + + strings.slice memory lineDelim = "\n".toSlice(); + strings.slice memory keyDelim = "=".toSlice(); + strings.slice memory commentDelim = "#".toSlice(); + + uint256 length = data.count(lineDelim) + 1; + for (uint256 i; i < length; ++i) { + strings.slice memory line = data.split(lineDelim); + if (!line.startsWith(commentDelim)) { + string memory key = line.split(keyDelim).toString(); + // Ignore empty lines + if (bytes(key).length != 0) { + vm.setEnv(key, line.toString()); + } + } + } + } + + function config() internal { + config(".env"); + } +} + +/* + * @title String & slice utility library for Solidity contracts. + * @author Nick Johnson + * + * @dev Functionality in this library is largely implemented using an + * abstraction called a 'slice'. A slice represents a part of a string - + * anything from the entire string to a single character, or even no + * characters at all (a 0-length slice). Since a slice only has to specify + * an offset and a length, copying and manipulating slices is a lot less + * expensive than copying and manipulating the strings they reference. + * + * To further reduce gas costs, most functions on slice that need to return + * a slice modify the original one instead of allocating a new one; for + * instance, `s.split(".")` will return the text up to the first '.', + * modifying s to only contain the remainder of the string after the '.'. + * In situations where you do not want to modify the original slice, you + * can make a copy first with `.copy()`, for example: + * `s.copy().split(".")`. Try and avoid using this idiom in loops; since + * Solidity has no memory management, it will result in allocating many + * short-lived slices that are later discarded. + * + * Functions that return two slices come in two versions: a non-allocating + * version that takes the second slice as an argument, modifying it in + * place, and an allocating version that allocates and returns the second + * slice; see `nextRune` for example. + * + * Functions that have to copy string data will return strings rather than + * slices; these can be cast back to slices for further processing if + * required. + * + * For convenience, some functions are provided with non-modifying + * variants that create a new slice and return both; for instance, + * `s.splitNew('.')` leaves s unmodified, and returns two values + * corresponding to the left and right parts of the string. + */ + +pragma solidity ^0.8.0; + +library strings { + struct slice { + uint256 _len; + uint256 _ptr; + } + + function memcpy(uint256 dest, uint256 src, uint256 len) private pure { + // Copy word-length chunks while possible + for (; len >= 32; len -= 32) { + assembly { + mstore(dest, mload(src)) + } + dest += 32; + src += 32; + } + + // Copy remaining bytes + uint256 mask = type(uint256).max; + if (len > 0) { + mask = 256 ** (32 - len) - 1; + } + assembly { + let srcpart := and(mload(src), not(mask)) + let destpart := and(mload(dest), mask) + mstore(dest, or(destpart, srcpart)) + } + } + + /* + * @dev Returns a slice containing the entire string. + * @param self The string to make a slice from. + * @return A newly allocated slice containing the entire string. + */ + function toSlice(string memory self) internal pure returns (slice memory) { + uint256 ptr; + assembly { + ptr := add(self, 0x20) + } + return slice(bytes(self).length, ptr); + } + + /* + * @dev Returns the length of a null-terminated bytes32 string. + * @param self The value to find the length of. + * @return The length of the string, from 0 to 32. + */ + function len(bytes32 self) internal pure returns (uint256) { + uint256 ret; + if (self == 0) return 0; + if (uint256(self) & type(uint128).max == 0) { + ret += 16; + self = bytes32(uint256(self) / 0x100000000000000000000000000000000); + } + if (uint256(self) & type(uint64).max == 0) { + ret += 8; + self = bytes32(uint256(self) / 0x10000000000000000); + } + if (uint256(self) & type(uint32).max == 0) { + ret += 4; + self = bytes32(uint256(self) / 0x100000000); + } + if (uint256(self) & type(uint16).max == 0) { + ret += 2; + self = bytes32(uint256(self) / 0x10000); + } + if (uint256(self) & type(uint8).max == 0) { + ret += 1; + } + return 32 - ret; + } + + /* + * @dev Returns a slice containing the entire bytes32, interpreted as a + * null-terminated utf-8 string. + * @param self The bytes32 value to convert to a slice. + * @return A new slice containing the value of the input argument up to the + * first null. + */ + function toSliceB32(bytes32 self) internal pure returns (slice memory ret) { + // Allocate space for `self` in memory, copy it there, and point ret at it + assembly { + let ptr := mload(0x40) + mstore(0x40, add(ptr, 0x20)) + mstore(ptr, self) + mstore(add(ret, 0x20), ptr) + } + ret._len = len(self); + } + + /* + * @dev Returns a new slice containing the same data as the current slice. + * @param self The slice to copy. + * @return A new slice containing the same data as `self`. + */ + function copy(slice memory self) internal pure returns (slice memory) { + return slice(self._len, self._ptr); + } + + /* + * @dev Copies a slice to a new string. + * @param self The slice to copy. + * @return A newly allocated string containing the slice's text. + */ + function toString(slice memory self) internal pure returns (string memory) { + string memory ret = new string(self._len); + uint256 retptr; + assembly { + retptr := add(ret, 32) + } + + memcpy(retptr, self._ptr, self._len); + return ret; + } + + /* + * @dev Returns the length in runes of the slice. Note that this operation + * takes time proportional to the length of the slice; avoid using it + * in loops, and call `slice.empty()` if you only need to know whether + * the slice is empty or not. + * @param self The slice to operate on. + * @return The length of the slice in runes. + */ + function len(slice memory self) internal pure returns (uint256 l) { + // Starting at ptr-31 means the LSB will be the byte we care about + uint256 ptr = self._ptr - 31; + uint256 end = ptr + self._len; + for (l = 0; ptr < end; l++) { + uint8 b; + assembly { + b := and(mload(ptr), 0xFF) + } + if (b < 0x80) { + ptr += 1; + } else if (b < 0xE0) { + ptr += 2; + } else if (b < 0xF0) { + ptr += 3; + } else if (b < 0xF8) { + ptr += 4; + } else if (b < 0xFC) { + ptr += 5; + } else { + ptr += 6; + } + } + } + + /* + * @dev Returns true if the slice is empty (has a length of 0). + * @param self The slice to operate on. + * @return True if the slice is empty, False otherwise. + */ + function empty(slice memory self) internal pure returns (bool) { + return self._len == 0; + } + + /* + * @dev Returns a positive number if `other` comes lexicographically after + * `self`, a negative number if it comes before, or zero if the + * contents of the two slices are equal. Comparison is done per-rune, + * on unicode codepoints. + * @param self The first slice to compare. + * @param other The second slice to compare. + * @return The result of the comparison. + */ + function compare(slice memory self, slice memory other) internal pure returns (int256) { + uint256 shortest = self._len; + if (other._len < self._len) shortest = other._len; + + uint256 selfptr = self._ptr; + uint256 otherptr = other._ptr; + for (uint256 idx = 0; idx < shortest; idx += 32) { + uint256 a; + uint256 b; + assembly { + a := mload(selfptr) + b := mload(otherptr) + } + if (a != b) { + // Mask out irrelevant bytes and check again + uint256 mask = type(uint256).max; // 0xffff... + if (shortest < 32) { + mask = ~(2 ** (8 * (32 - shortest + idx)) - 1); + } + unchecked { + uint256 diff = (a & mask) - (b & mask); + if (diff != 0) return int256(diff); + } + } + selfptr += 32; + otherptr += 32; + } + return int256(self._len) - int256(other._len); + } + + /* + * @dev Returns true if the two slices contain the same text. + * @param self The first slice to compare. + * @param self The second slice to compare. + * @return True if the slices are equal, false otherwise. + */ + function equals(slice memory self, slice memory other) internal pure returns (bool) { + return compare(self, other) == 0; + } + + /* + * @dev Extracts the first rune in the slice into `rune`, advancing the + * slice to point to the next rune and returning `self`. + * @param self The slice to operate on. + * @param rune The slice that will contain the first rune. + * @return `rune`. + */ + function nextRune(slice memory self, slice memory rune) internal pure returns (slice memory) { + rune._ptr = self._ptr; + + if (self._len == 0) { + rune._len = 0; + return rune; + } + + uint256 l; + uint256 b; + // Load the first byte of the rune into the LSBs of b + assembly { + b := and(mload(sub(mload(add(self, 32)), 31)), 0xFF) + } + if (b < 0x80) { + l = 1; + } else if (b < 0xE0) { + l = 2; + } else if (b < 0xF0) { + l = 3; + } else { + l = 4; + } + + // Check for truncated codepoints + if (l > self._len) { + rune._len = self._len; + self._ptr += self._len; + self._len = 0; + return rune; + } + + self._ptr += l; + self._len -= l; + rune._len = l; + return rune; + } + + /* + * @dev Returns the first rune in the slice, advancing the slice to point + * to the next rune. + * @param self The slice to operate on. + * @return A slice containing only the first rune from `self`. + */ + function nextRune(slice memory self) internal pure returns (slice memory ret) { + nextRune(self, ret); + } + + /* + * @dev Returns the number of the first codepoint in the slice. + * @param self The slice to operate on. + * @return The number of the first codepoint in the slice. + */ + function ord(slice memory self) internal pure returns (uint256 ret) { + if (self._len == 0) { + return 0; + } + + uint256 word; + uint256 length; + uint256 divisor = 2 ** 248; + + // Load the rune into the MSBs of b + assembly { + word := mload(mload(add(self, 32))) + } + uint256 b = word / divisor; + if (b < 0x80) { + ret = b; + length = 1; + } else if (b < 0xE0) { + ret = b & 0x1F; + length = 2; + } else if (b < 0xF0) { + ret = b & 0x0F; + length = 3; + } else { + ret = b & 0x07; + length = 4; + } + + // Check for truncated codepoints + if (length > self._len) { + return 0; + } + + for (uint256 i = 1; i < length; ++i) { + divisor = divisor / 256; + b = (word / divisor) & 0xFF; + if (b & 0xC0 != 0x80) { + // Invalid UTF-8 sequence + return 0; + } + ret = (ret * 64) | (b & 0x3F); + } + + return ret; + } + + /* + * @dev Returns the keccak-256 hash of the slice. + * @param self The slice to hash. + * @return The hash of the slice. + */ + function keccak(slice memory self) internal pure returns (bytes32 ret) { + assembly { + ret := keccak256(mload(add(self, 32)), mload(self)) + } + } + + /* + * @dev Returns true if `self` starts with `needle`. + * @param self The slice to operate on. + * @param needle The slice to search for. + * @return True if the slice starts with the provided text, false otherwise. + */ + function startsWith(slice memory self, slice memory needle) internal pure returns (bool) { + if (self._len < needle._len) { + return false; + } + + if (self._ptr == needle._ptr) { + return true; + } + + bool equal; + assembly { + let length := mload(needle) + let selfptr := mload(add(self, 0x20)) + let needleptr := mload(add(needle, 0x20)) + equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) + } + return equal; + } + + /* + * @dev If `self` starts with `needle`, `needle` is removed from the + * beginning of `self`. Otherwise, `self` is unmodified. + * @param self The slice to operate on. + * @param needle The slice to search for. + * @return `self` + */ + function beyond(slice memory self, slice memory needle) internal pure returns (slice memory) { + if (self._len < needle._len) { + return self; + } + + bool equal = true; + if (self._ptr != needle._ptr) { + assembly { + let length := mload(needle) + let selfptr := mload(add(self, 0x20)) + let needleptr := mload(add(needle, 0x20)) + equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) + } + } + + if (equal) { + self._len -= needle._len; + self._ptr += needle._len; + } + + return self; + } + + /* + * @dev Returns true if the slice ends with `needle`. + * @param self The slice to operate on. + * @param needle The slice to search for. + * @return True if the slice starts with the provided text, false otherwise. + */ + function endsWith(slice memory self, slice memory needle) internal pure returns (bool) { + if (self._len < needle._len) { + return false; + } + + uint256 selfptr = self._ptr + self._len - needle._len; + + if (selfptr == needle._ptr) { + return true; + } + + bool equal; + assembly { + let length := mload(needle) + let needleptr := mload(add(needle, 0x20)) + equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) + } + + return equal; + } + + /* + * @dev If `self` ends with `needle`, `needle` is removed from the + * end of `self`. Otherwise, `self` is unmodified. + * @param self The slice to operate on. + * @param needle The slice to search for. + * @return `self` + */ + function until(slice memory self, slice memory needle) internal pure returns (slice memory) { + if (self._len < needle._len) { + return self; + } + + uint256 selfptr = self._ptr + self._len - needle._len; + bool equal = true; + if (selfptr != needle._ptr) { + assembly { + let length := mload(needle) + let needleptr := mload(add(needle, 0x20)) + equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) + } + } + + if (equal) { + self._len -= needle._len; + } + + return self; + } + + // Returns the memory address of the first byte of the first occurrence of + // `needle` in `self`, or the first byte after `self` if not found. + function findPtr( + uint256 selflen, + uint256 selfptr, + uint256 needlelen, + uint256 needleptr + ) private pure returns (uint256) { + uint256 ptr = selfptr; + uint256 idx; + + if (needlelen <= selflen) { + if (needlelen <= 32) { + bytes32 mask; + if (needlelen > 0) { + mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1)); + } + + bytes32 needledata; + assembly { + needledata := and(mload(needleptr), mask) + } + + uint256 end = selfptr + selflen - needlelen; + bytes32 ptrdata; + assembly { + ptrdata := and(mload(ptr), mask) + } + + while (ptrdata != needledata) { + if (ptr >= end) return selfptr + selflen; + ptr++; + assembly { + ptrdata := and(mload(ptr), mask) + } + } + return ptr; + } else { + // For long needles, use hashing + bytes32 hash; + assembly { + hash := keccak256(needleptr, needlelen) + } + + for (idx = 0; idx <= selflen - needlelen; idx++) { + bytes32 testHash; + assembly { + testHash := keccak256(ptr, needlelen) + } + if (hash == testHash) return ptr; + ptr += 1; + } + } + } + return selfptr + selflen; + } + + // Returns the memory address of the first byte after the last occurrence of + // `needle` in `self`, or the address of `self` if not found. + function rfindPtr( + uint256 selflen, + uint256 selfptr, + uint256 needlelen, + uint256 needleptr + ) private pure returns (uint256) { + uint256 ptr; + + if (needlelen <= selflen) { + if (needlelen <= 32) { + bytes32 mask; + if (needlelen > 0) { + mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1)); + } + + bytes32 needledata; + assembly { + needledata := and(mload(needleptr), mask) + } + + ptr = selfptr + selflen - needlelen; + bytes32 ptrdata; + assembly { + ptrdata := and(mload(ptr), mask) + } + + while (ptrdata != needledata) { + if (ptr <= selfptr) return selfptr; + ptr--; + assembly { + ptrdata := and(mload(ptr), mask) + } + } + return ptr + needlelen; + } else { + // For long needles, use hashing + bytes32 hash; + assembly { + hash := keccak256(needleptr, needlelen) + } + ptr = selfptr + (selflen - needlelen); + while (ptr >= selfptr) { + bytes32 testHash; + assembly { + testHash := keccak256(ptr, needlelen) + } + if (hash == testHash) return ptr + needlelen; + ptr -= 1; + } + } + } + return selfptr; + } + + /* + * @dev Modifies `self` to contain everything from the first occurrence of + * `needle` to the end of the slice. `self` is set to the empty slice + * if `needle` is not found. + * @param self The slice to search and modify. + * @param needle The text to search for. + * @return `self`. + */ + function find(slice memory self, slice memory needle) internal pure returns (slice memory) { + uint256 ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr); + self._len -= ptr - self._ptr; + self._ptr = ptr; + return self; + } + + /* + * @dev Modifies `self` to contain the part of the string from the start of + * `self` to the end of the first occurrence of `needle`. If `needle` + * is not found, `self` is set to the empty slice. + * @param self The slice to search and modify. + * @param needle The text to search for. + * @return `self`. + */ + function rfind(slice memory self, slice memory needle) internal pure returns (slice memory) { + uint256 ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr); + self._len = ptr - self._ptr; + return self; + } + + /* + * @dev Splits the slice, setting `self` to everything after the first + * occurrence of `needle`, and `token` to everything before it. If + * `needle` does not occur in `self`, `self` is set to the empty slice, + * and `token` is set to the entirety of `self`. + * @param self The slice to split. + * @param needle The text to search for in `self`. + * @param token An output parameter to which the first token is written. + * @return `token`. + */ + function split(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) { + uint256 ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr); + token._ptr = self._ptr; + token._len = ptr - self._ptr; + if (ptr == self._ptr + self._len) { + // Not found + self._len = 0; + } else { + self._len -= token._len + needle._len; + self._ptr = ptr + needle._len; + } + return token; + } + + /* + * @dev Splits the slice, setting `self` to everything after the first + * occurrence of `needle`, and returning everything before it. If + * `needle` does not occur in `self`, `self` is set to the empty slice, + * and the entirety of `self` is returned. + * @param self The slice to split. + * @param needle The text to search for in `self`. + * @return The part of `self` up to the first occurrence of `delim`. + */ + function split(slice memory self, slice memory needle) internal pure returns (slice memory token) { + split(self, needle, token); + } + + /* + * @dev Splits the slice, setting `self` to everything before the last + * occurrence of `needle`, and `token` to everything after it. If + * `needle` does not occur in `self`, `self` is set to the empty slice, + * and `token` is set to the entirety of `self`. + * @param self The slice to split. + * @param needle The text to search for in `self`. + * @param token An output parameter to which the first token is written. + * @return `token`. + */ + function rsplit(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) { + uint256 ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr); + token._ptr = ptr; + token._len = self._len - (ptr - self._ptr); + if (ptr == self._ptr) { + // Not found + self._len = 0; + } else { + self._len -= token._len + needle._len; + } + return token; + } + + /* + * @dev Splits the slice, setting `self` to everything before the last + * occurrence of `needle`, and returning everything after it. If + * `needle` does not occur in `self`, `self` is set to the empty slice, + * and the entirety of `self` is returned. + * @param self The slice to split. + * @param needle The text to search for in `self`. + * @return The part of `self` after the last occurrence of `delim`. + */ + function rsplit(slice memory self, slice memory needle) internal pure returns (slice memory token) { + rsplit(self, needle, token); + } + + /* + * @dev Counts the number of nonoverlapping occurrences of `needle` in `self`. + * @param self The slice to search. + * @param needle The text to search for in `self`. + * @return The number of occurrences of `needle` found in `self`. + */ + function count(slice memory self, slice memory needle) internal pure returns (uint256 cnt) { + uint256 ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr) + needle._len; + while (ptr <= self._ptr + self._len) { + cnt++; + ptr = findPtr(self._len - (ptr - self._ptr), ptr, needle._len, needle._ptr) + needle._len; + } + } + + /* + * @dev Returns True if `self` contains `needle`. + * @param self The slice to search. + * @param needle The text to search for in `self`. + * @return True if `needle` is found in `self`, false otherwise. + */ + function contains(slice memory self, slice memory needle) internal pure returns (bool) { + return rfindPtr(self._len, self._ptr, needle._len, needle._ptr) != self._ptr; + } + + /* + * @dev Returns a newly allocated string containing the concatenation of + * `self` and `other`. + * @param self The first slice to concatenate. + * @param other The second slice to concatenate. + * @return The concatenation of the two strings. + */ + function concat(slice memory self, slice memory other) internal pure returns (string memory) { + string memory ret = new string(self._len + other._len); + uint256 retptr; + assembly { + retptr := add(ret, 32) + } + memcpy(retptr, self._ptr, self._len); + memcpy(retptr + self._len, other._ptr, other._len); + return ret; + } + + /* + * @dev Joins an array of slices, using `self` as a delimiter, returning a + * newly allocated string. + * @param self The delimiter to use. + * @param parts A list of slices to join. + * @return A newly allocated string containing all the slices in `parts`, + * joined with `self`. + */ + function join(slice memory self, slice[] memory parts) internal pure returns (string memory) { + if (parts.length == 0) return ""; + + uint256 length = self._len * (parts.length - 1); + for (uint256 i; i < parts.length; ++i) length += parts[i]._len; + + string memory ret = new string(length); + uint256 retptr; + assembly { + retptr := add(ret, 32) + } + + for (uint256 i; i < parts.length; ++i) { + memcpy(retptr, parts[i]._ptr, parts[i]._len); + retptr += parts[i]._len; + if (i < parts.length - 1) { + memcpy(retptr, self._ptr, self._len); + retptr += self._len; + } + } + + return ret; + } +} diff --git a/utils/forwardUtils.js b/utils/forwardUtils.js new file mode 100755 index 0000000..60e7e28 --- /dev/null +++ b/utils/forwardUtils.js @@ -0,0 +1,22 @@ +const { exec } = require("child_process"); + +if (process.argv.length < 3) { + console.error('Please provide a chain input as an argument.'); + process.exit(1); +} + +const command = process.argv[2]; +const extraArgs = process.argv.slice(3).join(' '); + + +exec(`node lib/utils/utils/${command}.js ${extraArgs}`, (error, stdout, stderr) => { + if (error) { + console.log(error); + return; + } + if (stderr) { + console.log(stderr); + return; + } + console.log(stdout); +}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..1edf638 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1691 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@angleprotocol/sdk@0.38.8": + version "0.38.8" + resolved "https://npm.pkg.github.com/download/@angleprotocol/sdk/0.38.8/dde9467c39c877b5c928791f2e1f7a2c5e92cbbb#dde9467c39c877b5c928791f2e1f7a2c5e92cbbb" + integrity sha512-mHosGBuF03jXcCTWUqsP6BV7ukoKCEugWC0zIHdTXboSERrWj9T36xlecwjO+6PnbbCS16oc/9OBUmlH7nrqZQ== + dependencies: + "@apollo/client" "^3.7.17" + "@typechain/ethers-v5" "^10.0.0" + "@types/lodash" "^4.14.180" + ethers "^5.6.4" + graphql "^15.7.1" + graphql-request "^3.6.1" + jsbi "^4.3.0" + keccak256 "^1.0.6" + lodash "^4.17.21" + merkletreejs "^0.3.10" + tiny-invariant "^1.1.0" + typechain "^8.3.2" + +"@apollo/client@^3.7.17": + version "3.9.11" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.9.11.tgz#737e5c35c21d6f3b78423033ad81837a8a6992e0" + integrity sha512-H7e9m7cRcFO93tokwzqrsbnfKorkpV24xU30hFH5u2g6B+c1DMo/ouyF/YrBPdrTzqxQCjTUmds/FLmJ7626GA== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + "@wry/caches" "^1.0.0" + "@wry/equality" "^0.5.6" + "@wry/trie" "^0.5.0" + graphql-tag "^2.12.6" + hoist-non-react-statics "^3.3.2" + optimism "^0.18.0" + prop-types "^15.7.2" + rehackt "0.0.6" + response-iterator "^0.2.6" + symbol-observable "^4.0.0" + ts-invariant "^0.10.3" + tslib "^2.3.0" + zen-observable-ts "^1.2.5" + +"@babel/code-frame@^7.0.0": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + +"@ethereumjs/util@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" + integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + ethereum-cryptography "^2.0.0" + micro-ftch "^0.3.1" + +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + +"@ethersproject/contracts@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@5.7.2": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/solidity@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/wallet@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@graphql-typed-document-node/core@^3.1.1": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" + integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== + +"@noble/curves@1.3.0", "@noble/curves@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" + integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== + dependencies: + "@noble/hashes" "1.3.3" + +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.2": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== + +"@noble/hashes@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + +"@scure/base@~1.1.4": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" + integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== + +"@scure/bip32@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8" + integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ== + dependencies: + "@noble/curves" "~1.3.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + +"@scure/bip39@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527" + integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA== + dependencies: + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + +"@solidity-parser/parser@^0.16.0": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.16.2.tgz#42cb1e3d88b3e8029b0c9befff00b634cd92d2fa" + integrity sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg== + dependencies: + antlr4ts "^0.5.0-alpha.4" + +"@solidity-parser/parser@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.17.0.tgz#52a2fcc97ff609f72011014e4c5b485ec52243ef" + integrity sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw== + +"@typechain/ethers-v5@^10.0.0": + version "10.2.1" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.2.1.tgz#50241e6957683281ecfa03fb5a6724d8a3ce2391" + integrity sha512-n3tQmCZjRE6IU4h6lqUGiQ1j866n5MTCBJreNEHHVWXa2u9GJTaeYyU1/k+1qLutkyw+sS6VAN+AbeiTqsxd/A== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + +"@types/lodash@^4.14.180": + version "4.17.0" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" + integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA== + +"@types/prettier@^2.1.1": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== + +"@wry/caches@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@wry/caches/-/caches-1.0.1.tgz#8641fd3b6e09230b86ce8b93558d44cf1ece7e52" + integrity sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA== + dependencies: + tslib "^2.3.0" + +"@wry/context@^0.7.0": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.7.4.tgz#e32d750fa075955c4ab2cfb8c48095e1d42d5990" + integrity sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ== + dependencies: + tslib "^2.3.0" + +"@wry/equality@^0.5.6": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.7.tgz#72ec1a73760943d439d56b7b1e9985aec5d497bb" + integrity sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw== + dependencies: + tslib "^2.3.0" + +"@wry/trie@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.4.3.tgz#077d52c22365871bf3ffcbab8e95cb8bc5689af4" + integrity sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w== + dependencies: + tslib "^2.3.0" + +"@wry/trie@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.5.0.tgz#11e783f3a53f6e4cd1d42d2d1323f5bc3fa99c94" + integrity sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA== + dependencies: + tslib "^2.3.0" + +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + +ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.1: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +antlr4@^4.11.0: + version "4.13.1" + resolved "https://registry.yarnpkg.com/antlr4/-/antlr4-4.13.1.tgz#1e0a1830a08faeb86217cb2e6c34716004e4253d" + integrity sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA== + +antlr4ts@^0.5.0-alpha.4: + version "0.5.0-alpha.4" + resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" + integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^4.0.1, array-back@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" + integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== + +ast-parents@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/ast-parents/-/ast-parents-0.0.1.tgz#508fd0f05d0c48775d9eccda2e174423261e8dd3" + integrity sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +bignumber.js@^9.0.1: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + +bn.js@4.11.6: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.2.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +buffer-reverse@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" + integrity sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-line-args@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-usage@^6.1.0: + version "6.1.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" + integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== + dependencies: + array-back "^4.0.2" + chalk "^2.4.2" + table-layout "^1.0.2" + typical "^5.2.0" + +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cosmiconfig@^8.0.0: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +cross-fetch@^3.0.6: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + +debug@^4.3.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +elliptic@6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +ethereum-bloom-filters@^1.0.6: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.1.0.tgz#b3fc1eb789509ee30db0bf99a2988ccacb8d0397" + integrity sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw== + dependencies: + "@noble/hashes" "^1.4.0" + +ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a" + integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA== + dependencies: + "@noble/curves" "1.3.0" + "@noble/hashes" "1.3.3" + "@scure/bip32" "1.3.3" + "@scure/bip39" "1.2.2" + +ethers@^5.6.4: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + +ethjs-unit@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" + integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw== + dependencies: + bn.js "4.11.6" + number-to-bn "1.7.0" + +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2, fast-diff@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +glob@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphql-request@^3.6.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.7.0.tgz#c7406e537084f8b9788541e3e6704340ca13055b" + integrity sha512-dw5PxHCgBneN2DDNqpWu8QkbbJ07oOziy8z+bK/TAXufsOLaETuVO4GkXrbs0WjhdKhBMN3BkpN/RIvUHkmNUQ== + dependencies: + cross-fetch "^3.0.6" + extract-files "^9.0.0" + form-data "^3.0.0" + +graphql-tag@^2.12.6: + version "2.12.6" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" + integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== + dependencies: + tslib "^2.1.0" + +graphql@^15.7.1: + version "15.8.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" + integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.4: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-hex-prefixed@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" + integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== + +js-sha3@0.8.0, js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsbi@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.0.tgz#b54ee074fb6fcbc00619559305c8f7e912b04741" + integrity sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +keccak256@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/keccak256/-/keccak256-1.0.6.tgz#dd32fb771558fed51ce4e45a035ae7515573da58" + integrity sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw== + dependencies: + bn.js "^5.2.0" + buffer "^6.0.3" + keccak "^3.0.2" + +keccak@^3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" + integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + +lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +merkletreejs@^0.3.10: + version "0.3.11" + resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.3.11.tgz#e0de05c3ca1fd368de05a12cb8efb954ef6fc04f" + integrity sha512-LJKTl4iVNTndhL+3Uz/tfkjD0klIWsHlUzgtuNnNrsf7bAlXR30m+xYB7lHr5Z/l6e/yAIsr26Dabx6Buo4VGQ== + dependencies: + bignumber.js "^9.0.1" + buffer-reverse "^1.0.1" + crypto-js "^4.2.0" + treeify "^1.1.0" + web3-utils "^1.3.4" + +micro-ftch@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" + integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + +node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-gyp-build@^4.2.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" + integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== + +number-to-bn@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" + integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig== + dependencies: + bn.js "4.11.6" + strip-hex-prefix "1.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optimism@^0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.18.0.tgz#e7bb38b24715f3fdad8a9a7fc18e999144bbfa63" + integrity sha512-tGn8+REwLRNFnb9WmcY5IfpOqeX2kpaYJ1s6Ae3mn12AeydLkR3j+jSCmVQFoXqU8D41PAJ1RG1rCRNWmNZVmQ== + dependencies: + "@wry/caches" "^1.0.0" + "@wry/context" "^0.7.0" + "@wry/trie" "^0.4.3" + tslib "^2.3.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier-plugin-solidity@^1.1.3: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz#59944d3155b249f7f234dee29f433524b9a4abcf" + integrity sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA== + dependencies: + "@solidity-parser/parser" "^0.17.0" + semver "^7.5.4" + solidity-comments-extractor "^0.0.8" + +prettier@^2.0.0, prettier@^2.3.1, prettier@^2.8.3: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +prop-types@^15.7.2: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +reduce-flatten@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== + +rehackt@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/rehackt/-/rehackt-0.0.6.tgz#7a0a2247f2295e7548915417e44fbbf03bf004f4" + integrity sha512-l3WEzkt4ntlEc/IB3/mF6SRgNHA6zfQR7BlGOgBTOmx7IJJXojDASav+NsgXHFjHn+6RmwqsGPFgZpabWpeOdw== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +response-iterator@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/response-iterator/-/response-iterator-0.2.6.tgz#249005fb14d2e4eeb478a3f735a28fd8b4c9f3da" + integrity sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw== + +safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +scrypt-js@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + +semver@^7.5.2, semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +solhint-plugin-prettier@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/solhint-plugin-prettier/-/solhint-plugin-prettier-0.0.5.tgz#e3b22800ba435cd640a9eca805a7f8bc3e3e6a6b" + integrity sha512-7jmWcnVshIrO2FFinIvDQmhQpfpS2rRRn3RejiYgnjIE68xO2bvrYvjqVNfrio4xH9ghOqn83tKuTzLjEbmGIA== + dependencies: + prettier-linter-helpers "^1.0.0" + +solhint@^3.5.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/solhint/-/solhint-3.6.2.tgz#2b2acbec8fdc37b2c68206a71ba89c7f519943fe" + integrity sha512-85EeLbmkcPwD+3JR7aEMKsVC9YrRSxd4qkXuMzrlf7+z2Eqdfm1wHWq1ffTuo5aDhoZxp2I9yF3QkxZOxOL7aQ== + dependencies: + "@solidity-parser/parser" "^0.16.0" + ajv "^6.12.6" + antlr4 "^4.11.0" + ast-parents "^0.0.1" + chalk "^4.1.2" + commander "^10.0.0" + cosmiconfig "^8.0.0" + fast-diff "^1.2.0" + glob "^8.0.3" + ignore "^5.2.4" + js-yaml "^4.1.0" + lodash "^4.17.21" + pluralize "^8.0.0" + semver "^7.5.2" + strip-ansi "^6.0.1" + table "^6.8.1" + text-table "^0.2.0" + optionalDependencies: + prettier "^2.8.3" + +solidity-comments-extractor@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz#f6e148ab0c49f30c1abcbecb8b8df01ed8e879f8" + integrity sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g== + +string-format@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" + integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== + +string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-hex-prefix@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" + integrity sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A== + dependencies: + is-hex-prefixed "1.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +symbol-observable@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== + +table-layout@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" + integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== + dependencies: + array-back "^4.0.1" + deep-extend "~0.6.0" + typical "^5.2.0" + wordwrapjs "^4.0.0" + +table@^6.8.1: + version "6.8.2" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" + integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tiny-invariant@^1.1.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +treeify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== + +ts-command-line-args@^2.2.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz#e64456b580d1d4f6d948824c274cf6fa5f45f7f0" + integrity sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw== + dependencies: + chalk "^4.1.0" + command-line-args "^5.1.1" + command-line-usage "^6.1.0" + string-format "^2.0.0" + +ts-essentials@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + +ts-invariant@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.10.3.tgz#3e048ff96e91459ffca01304dbc7f61c1f642f6c" + integrity sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ== + dependencies: + tslib "^2.1.0" + +tslib@^2.1.0, tslib@^2.3.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +typechain@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73" + integrity sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q== + dependencies: + "@types/prettier" "^2.1.1" + debug "^4.3.1" + fs-extra "^7.0.0" + glob "7.1.7" + js-sha3 "^0.8.0" + lodash "^4.17.15" + mkdirp "^1.0.4" + prettier "^2.3.1" + ts-command-line-args "^2.2.0" + ts-essentials "^7.0.1" + +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +utf8@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +web3-utils@^1.3.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec" + integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A== + dependencies: + "@ethereumjs/util" "^8.1.0" + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereum-cryptography "^2.1.2" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wordwrapjs@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" + integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== + dependencies: + reduce-flatten "^2.0.0" + typical "^5.2.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +zen-observable-ts@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58" + integrity sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg== + dependencies: + zen-observable "0.8.15" + +zen-observable@0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" + integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==