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 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==