diff --git a/.claude/rules/solidity-style.md b/.claude/rules/solidity-style.md new file mode 100644 index 00000000..55701016 --- /dev/null +++ b/.claude/rules/solidity-style.md @@ -0,0 +1,145 @@ +--- +description: Solidity coding standards for all contract and interface files +globs: contracts/**/*.sol +--- + +# Solidity Style Rules + +## Contract Layout (top -> bottom) + +1. Constants (`public constant`) +2. Immutables (`public immutable` or `internal immutable`) +3. State variables (`public` for auto-getters where possible; `internal` for structs -- see Function Visibility) +4. Events +5. Errors (custom errors only -- **no `require` with strings**) +6. Modifiers +7. Constructor / `initialize` +8. `receive` / `fallback` +9. Functions (ordered by visibility, see below) + +--- + +## Function Ordering + +Within the functions section, order by visibility: + +`external` -> `public` -> `internal` -> `private` + +Within each visibility group: + +1. ACM / access-gated (e.g. `onlyOwner`, `_checkAccessAllowed`) first +2. Permissionless second + +Within each access level: + +1. State-changing +2. `view` +3. `pure` + +--- + +## Function Visibility + +- **No `public` functions** -- Use `internal` helper + `external` wrapper instead. + - **Exception:** Inherited/overridden functions from OZ or other base contracts (e.g. `getPrice()`, `initialize()`). +- State variables **should** be `public` (auto-getter). Define the corresponding getter signature in the interface. + - **Exception:** Struct state variables -- Solidity `public` structs generate flattened return values (one value per field), not a full struct return. Use `internal` storage + explicit `external` getter that returns the struct from `memory`. + - **Exception:** Array state variables -- Solidity `public` array auto-getters only allow index-based access. Keep the array `public` (index-based access via auto-getter), but also add an explicit `external` getter that returns the full array. + +--- + +## Constants & Immutables + +- Constants in contracts **must** be `public constant` (auto-getter). +- Constants in `library` contracts **must** be `internal constant` (Solidity restriction -- libraries cannot have `public` state). +- Contract addresses that will never change (e.g. Venus, OZ dependencies) **must** be `immutable` or `constant` -- never stored in regular state variables. +- Upgradeable contracts **may** have a `constructor` but **only** to set `immutable` variables. All other initialisation goes in `initialize()`. + +--- + +## NatSpec + +**Comment style:** + +- **Multiline** NatSpec (2+ tags or long descriptions) -> use `/** ... */` block comments. +- **Single-line** NatSpec (one short tag) -> use `///` inline comments. + +**Required on all `external` and `public` functions** and their interface declarations: + +- `@notice` -- what the function does +- `@param` -- each parameter +- `@return` -- each return value +- `@custom:error` -- each custom error the function can revert with +- `@custom:event` -- each event the function can emit + +**Required on all `internal` functions:** + +- `@notice` -- what the function does (brief is fine) +- `@param` -- each parameter +- `@return` -- each return value +- `@custom:error` -- each custom error the function can revert with (integrators of the calling `external` function inherit these) + +**Error attribution rule:** + +- `external`/`public` functions document **only** errors thrown directly in their own body. +- Errors originating from `internal` helpers are documented on those `internal` functions instead. +- **Interface declarations** may include the full list of possible errors (direct + internal) for integrator convenience. Only include errors added by the feature contract -- no need to document errors from imported OZ or ACM base contracts. + +--- + +## Caching + +- **Cache everything** -- Never SLOAD or external-call the same value twice. Cache in local variables. +- Copy storage structs to `memory` at function entry when reading multiple fields. +- Cache `msg.sender`, `block.timestamp`, array lengths, and repeated mapping lookups. + +--- + +## Errors & Events Placement + +- **Custom errors only** -- never `require(condition, "string")`. +- For contracts **around ~500 lines or fewer**: define errors and events in the contract itself. +- For contracts **over ~600 lines**: move **all** errors and events (existing ones included) to the interface -- not just new ones. This keeps the contract focused on logic only. +- Prefix errors with the contract/interface name context (e.g. `OracleNotEnabled`, `InvalidBoundRatio`). + +--- + +## Security + +- **CEI pattern** -- Always follow Checks-Effects-Interactions order. Update all state before making any external calls. +- **SafeERC20** -- Always use `SafeERC20` for token transfers. For approvals always use `forceApprove` (never raw `.approve()`). +- **Zero address validation** -- Always validate against `address(0)` for all address parameters in constructors and `initialize()`. + +--- + +## Events + +- Setter functions **must** emit events logging both the old and new value. +- If the old value is only needed for the event (not for any logic), emit **before** writing storage to avoid an unnecessary cache variable. This is safe for ACM-guarded setters since they are access-controlled and carry no re-entrancy risk: + ```solidity + // preferred -- emit before write, no cache needed + emit ValueUpdated(storedValue, newValue); + storedValue = newValue; + ``` +- If the old value is also needed for logic, cache it first: + ```solidity + // cache when old value is used in logic too + uint256 oldValue = storedValue; + storedValue = newValue; + emit ValueUpdated(oldValue, newValue); + ``` + +--- + +## General Style + +- **DRY** -- If the same logic is used in multiple places, extract it into a shared `internal` function and call it from all the relevant `external` functions. Never duplicate logic. +- Use named return variables only when it improves readability; otherwise use explicit `return`. +- Use `uint256` over `uint` -- always explicit bit width. +- Avoid magic numbers -- define named constants. +- One contract per file; filename matches contract name. +- Upgradeable contracts **must** declare a storage gap as the last state variable. The gap size should be `50` minus the number of storage slots already declared in that contract, so the total always sums to 50: + ```solidity + // e.g. contract declares 3 storage variables -> gap = 47 + uint256[47] private __gap; + ``` diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 00000000..c71ee637 --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,36 @@ +--- +description: Rules for when and how to write or update tests +globs: test/**/*.ts +--- + +# Testing Rules + +## When to Write Tests + +- During **feature implementation**, tests are written only on explicit request -- do not auto-generate tests while building contracts. +- Once implementation is complete (e.g. during fixes, reviews, or audits), tests are expected and should be written when asked. +- Once tests exist and code changes are made later: make the code change first, then immediately ask the user if you should update the tests before touching them. _(Also in CLAUDE.md so this rule is always in context.)_ +- Write tests against **intended behaviour**, not against what the code currently does. If a test fails, ask the user whether the behaviour is correct or the code has a bug -- never silently adjust a test to make it pass. + +--- + +## Assertions + +- **Always exact** -- `expect(actual).to.equal(expected)`. Weak assertions like `expect(balance).to.be.gt(0)` when the expected value is known are not acceptable. +- Use `gt` / `lt` only when the result is genuinely variable (e.g. a loss scenario). Even then, compute the expected value and assert it exactly or near-exactly. +- `closeTo` / approximate assertions only when rounding genuinely prevents an exact comparison -- never as a shortcut. + +--- + +## Events + +- Always assert the primary function-specific event -- not just underlying `Transfer` / `Approval`. +- Use `.to.emit(contract, "EventName").withArgs(...)` with all arguments verified. + +--- + +## Mocking + +- Prefer the real code path. Only mock external dependencies (oracles, external protocols) when there is no alternative. +- Use `@defi-wonderland/smock` for mocking external contracts. Prefer `smock.mock` — use `smock.fake` only when `smock.mock` cannot be used (e.g. the contract has no deployable artifact). +- Never mock internal contract behaviour -- test it directly. diff --git a/.claude/skills/feature/SKILL.md b/.claude/skills/feature/SKILL.md new file mode 100644 index 00000000..d252c60a --- /dev/null +++ b/.claude/skills/feature/SKILL.md @@ -0,0 +1,103 @@ +--- +name: feature +description: Start or resume feature development workflow -- restores context, creates feature doc, creates task plan, implements contracts, updates feature docs +argument-hint: +--- + +# Feature Development Workflow + +Working on feature: **$ARGUMENTS** + +## Execution Order + +1. **Read research** -- Read all files in `research/$ARGUMENTS/` if they exist +2. **Create feature doc** -- Write `feature-docs/$ARGUMENTS.md` by synthesising the research docs (see below) +3. **Create task plan** -- Write `.task_plan.md` -- primarily informed by the feature doc; may also reference research docs directly for additional detail +4. **Implement** -- Write contracts and interfaces following Solidity style rules (see `.claude/rules/solidity-style.md`) +5. **Update feature doc** -- Ongoing throughout implementation: log progress, decisions made, and any design changes +6. **Tests** -- Only when explicitly asked + +If `feature-docs/$ARGUMENTS.md` already exists, read it first to restore context, then continue from where work left off. + +--- + +## Feature Doc (`feature-docs/$ARGUMENTS.md`) + +The feature doc is the **single source of truth** for the feature. Create it before the task plan by synthesising the research docs. Keep it short and concise — capture only what is essential, not a full rewrite of the research. + +It should capture: + +- **What needs to be done** -- scope and goal of the feature +- **Where to make changes** -- which contracts, interfaces, and files are involved +- **How to make changes** -- implementation approach; if the research doc prescribes a specific approach follow it, otherwise use a subagent to analyse the codebase and determine the best approach before writing this section +- **Key design decisions** and **why** they were made +- **Trade-offs and gotchas** -- anything worth watching out for + +During implementation, keep this doc updated with actual progress and any decisions that deviate from the original plan. All design decisions go here — if something changes, update this doc. Research docs are input only: used once to create the initial feature doc and never updated after that. + +--- + +## Task Planning (`.task_plan.md`) + +Always create `.task_plan.md` at the project root before starting implementation. This file: + +- Contains the execution plan with phases and checklist +- Tracks progress, errors, and current status +- Is **not tracked by git** -- purely a working file +- Persists across sessions for the same feature -- append, don't overwrite + +--- + +## Research (`research/`) + +Research docs in `research/$ARGUMENTS/` feed the initial feature doc and are not touched after that. They can be generated by Claude during a research phase or pasted in directly by the user. They may contain: + +- **Design docs** -- architecture, interface design, flow diagrams +- **Implementation docs** -- step-by-step implementation details +- **Reference material** -- relevant protocol docs, external references +- **Analysis** -- security considerations, gas analysis, comparisons + +**Important**: Research docs (especially implementation docs) may contain code snippets -- treat these as pseudo-code for inspiration only, NOT as source of truth. Always write correct, bug-free logic yourself. If you see a better approach than what is described, always present it to the user -- explain what it is and why it is better -- and ask whether to use it instead. + +--- + +## File Structure + +New oracle contracts follow this layout: + +``` +contracts/ + ├── interfaces/IMyOracle.sol # Interface first + └── oracles/MyOracle.sol # Implementation + +test/ + └── MyOracle.ts # Unit tests (only when asked) + +deploy/ + └── NN-deploy-my-oracle.ts # Hardhat-deploy script + +feature-docs/ + └── $ARGUMENTS.md # Feature doc (git-tracked) + +research/ + └── $ARGUMENTS/ # Research input (NOT git-tracked) +``` + +--- + +## Subagent Strategy + +During active development, if asked something mid-work (explain code, find usages, compare approaches), use a subagent to handle it. This keeps the main context window clean — delegate the exploration, return only the answer. + +--- + +## Operational Rules + +1. **Ask freely** -- If anything is unclear or ambiguous, ask the user before proceeding. Never assume. +2. **Read before decide** -- Before major decisions, re-read the feature doc and plan file. +3. **No landmines** -- Find root causes, no temporary fixes. Leave no hidden hazards in the code. +4. **2-Action Rule** -- After every 2 search/browse operations, IMMEDIATELY save key findings to files. +5. **Update after act** -- After completing any phase: mark status, log errors, note files modified. +6. **Log ALL errors** -- Every error goes in the plan file. +7. **Never repeat failures** -- `if action_failed: next_action != same_action`. Track attempts, mutate approach. +8. **3-Strike Protocol** -- After 3 failed attempts at the same problem, escalate to user. diff --git a/.claude/skills/test/SKILL.md b/.claude/skills/test/SKILL.md new file mode 100644 index 00000000..58a40bf0 --- /dev/null +++ b/.claude/skills/test/SKILL.md @@ -0,0 +1,186 @@ +--- +name: test +description: Write Hardhat/TypeScript unit and fork tests for an oracle contract — reads the contract, studies existing test patterns, generates a comprehensive test file +argument-hint: +--- + +# Test Skill + +Write Hardhat tests for: **$ARGUMENTS** + +Follow all rules in `.claude/rules/testing.md` throughout. + +--- + +## Workflow + +Read the contract, its interface, and existing tests. Use subagents when needed. Draft a plan covering what to test and which paths to cover, present it to the user, and only proceed once confirmed. + +The guidance below applies to whichever test type the user requests. + +--- + +## Unit Tests + +One test file per contract in `test/`. The structure below is a reference — substitute actual contract names, addresses, and constructor args for the contract under test. + +```typescript +import { smock } from "@defi-wonderland/smock"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { BigNumberish } from "ethers"; +import { ethers, upgrades } from "hardhat"; + +import { AccessControlManager, ChainlinkOracle, MockV3Aggregator, ResilientOracle } from "../typechain-types"; + +// Helper: wire a fixed price for `asset` through a real Chainlink feed → ChainlinkOracle → ResilientOracle. +// Returns the MockV3Aggregator so callers can update the price later with `feed.updateAnswer(newPrice)`. +async function setAssetPrice( + resilientOracle: ResilientOracle, + chainlinkOracle: ChainlinkOracle, + asset: string, + price: BigNumberish, + maxStalePeriod = 60 * 60, +): Promise { + const feed = await (await ethers.getContractFactory("MockV3Aggregator")).deploy(8, price); + await chainlinkOracle.setTokenConfig({ asset, feed: feed.address, maxStalePeriod }); + await resilientOracle.setTokenConfig({ + asset, + oracles: [chainlinkOracle.address, ethers.constants.AddressZero, ethers.constants.AddressZero], + enableFlagsForOracles: [true, false, false], + cachingEnabled: false, + }); + return feed; +} + +describe("", async () => { + const fixture = async () => { + const [deployer, user] = await ethers.getSigners(); + + // ACM is an external dependency — mock it and allow all calls + const acm = await smock.mock("AccessControlManager"); + acm.isAllowedToCall.returns(true); + + // Real ChainlinkOracle + const ChainlinkOracleFactory = await ethers.getContractFactory("ChainlinkOracle"); + const chainlinkOracle = await upgrades.deployProxy(ChainlinkOracleFactory, [acm.address]); + + // Real ResilientOracle — pass nativeMarket, vai, boundValidator as needed + const ResilientOracleFactory = await ethers.getContractFactory("ResilientOracle"); + const resilientOracle = await upgrades.deployProxy(ResilientOracleFactory, [acm.address], { + constructorArgs: [nativeMarket, vai, boundValidator], + }); + + // Wire a price: feed returned for later updates via feed.updateAnswer(newPrice) + const feed = await setAssetPrice(resilientOracle, chainlinkOracle, asset.address, /* 8-decimal price */ BigNumber.from("100000000")); + + // deploy contract under test + const Factory = await ethers.getContractFactory(""); + const contract = await Factory.deploy(resilientOracle.address, ...); + + return { contract, resilientOracle, chainlinkOracle, feed, acm, deployer, user }; + }; + + let contract: ; + let feed: MockV3Aggregator; + + beforeEach(async () => { + ({ contract, feed, resilientOracle, chainlinkOracle, acm, deployer, user } = await loadFixture(fixture)); + }); + + // ──────────────────────────────────────────────────────────────────────── + // Section Name + // ──────────────────────────────────────────────────────────────────────── + + describe("getPrice", async () => { + it("should return correct price for valid asset", async () => { + // ... + }); + }); +}); +``` + +**Ordering:** Full path test first -- one comprehensive happy path test asserting every important value (return values, state, events). This is the canonical reference for how the feature behaves and makes manual review easy. Focused tests come after -- isolate a specific behaviour or edge case, with a comment explaining what is skipped and why: + +```typescript +// Full getPrice path covered in "should return correct price for valid asset". +// This test focuses on staleness check only -- price calculation assertions skipped. +``` + +**Patterns:** + +- **Fixture**: Use `loadFixture(fixture)` in `beforeEach` for snapshot isolation. +- **Mocks**: Use `smock.mock("IInterface")` for external dependencies (fall back to `smock.fake` only when no artifact exists); configure return values with `mock.functionName.returns(value)`. +- **Signers**: Use `contract.connect(signer).method()` to call as a specific address. +- **Reverts**: `await expect(tx).to.be.revertedWithCustomError(contract, "ErrorName")` -- never string-based. +- **Events**: `await expect(tx).to.emit(contract, "EventName").withArgs(arg1, arg2)`. +- **Time**: `await time.increase(seconds)` or `await time.setNextBlockTimestamp(ts)` from `@nomicfoundation/hardhat-network-helpers`. +- **Prices**: Always use real `ResilientOracle` + real `ChainlinkOracle` + `MockV3Aggregator`. Use the `setAssetPrice` helper (defined at the top of the file) to wire a price for an asset. To change a price mid-test: `await feed.updateAnswer(newPrice)` (8-decimal format). + +**Don't:** + +- Test OZ/base contract internals (Ownable, AccessControl, Initializable). +- Create new mocks if existing ones in `test/` suffice. +- Duplicate revert paths already tested in another file. +- Add fuzz tests unless explicitly asked. + +--- + +## Fork Tests + +Fork tests live in `test/fork/`. Run with `FORK=true FORKED_NETWORK=`. + +- Deploy the new contract inside the fork test itself — do not rely on a live deployment. +- Use real mainnet addresses for already-deployed contracts (oracles, resilientOracle, ACM) so live state is part of the test. +- Mirror the unit test flow but validate calculations against real on-chain data. +- Import shared helpers from `test/fork/utils.ts` (addresses, signers, impersonation helpers). + +```typescript +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { ethers } from "hardhat"; + +// Use live mainnet addresses — no mocks +const RESILIENT_ORACLE = "0x"; +const ACM = "0x"; + +describe(" fork tests", () => { + const fixture = async () => { + const [deployer] = await ethers.getSigners(); + + const resilientOracle = await ethers.getContractAt("ResilientOracle", RESILIENT_ORACLE); + + const Factory = await ethers.getContractFactory(""); + const contract = await Factory.deploy(resilientOracle.address, ...); + + return { contract, resilientOracle, deployer }; + }; + + let contract: ; + + beforeEach(async () => { + ({ contract, resilientOracle, deployer } = await loadFixture(fixture)); + }); + + it("returns correct price for on mainnet", async () => { + const price = await contract.getPrice(ASSET_ADDRESS); + // Assert against known on-chain value or a reasonable range + expect(price).to.be.gt(0); + }); +}); +``` + +--- + +## Verify + +Run `npx hardhat test test/.ts` and fix any failures before reporting to the user. + +--- + +## Post-deployment Changes + +This applies when a contract is already deployed in production and a subsequent PR introduces new functionality or changes — not the initial implementation PR. + +- Update the existing unit test file for the affected contract. +- Create a new focused test file covering all scenarios introduced or affected by the change. diff --git a/.eslinttsconfigrc b/.eslinttsconfigrc index d90f6b7f..14929d01 100644 --- a/.eslinttsconfigrc +++ b/.eslinttsconfigrc @@ -6,6 +6,7 @@ "test", "tasks", "docgen-templates", + "scripts", "commitlint.config.js", "./hardhat.config.zksync.ts" ] diff --git a/.solhint.json b/.solhint.json index e9676e29..8d908045 100644 --- a/.solhint.json +++ b/.solhint.json @@ -10,6 +10,6 @@ "max-line-length": ["error", 175], "not-rely-on-time": "warn", "reason-string": ["warn", { "maxLength": 64 }], - "ordering": "error" + "ordering": "warn" } } diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..d5459b88 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,134 @@ +# CLAUDE.md + +Venus Protocol Oracle — Solidity smart contract project using Hardhat. + +This is a DeFi protocol handling real funds. Always write clean, secure code — never apply quick patches or workarounds without understanding all side effects. Verify the impact of every change across the codebase. Every task deserves the same level of rigour; nothing should be treated as low priority or left at low quality. Never assume — if in doubt, ask and confirm before proceeding. + +--- + +## Package Manager + +Use **yarn** — never use npm. + +```bash +yarn install # install dependencies +``` + +--- + +## Common Commands + +```bash +yarn compile # compile contracts (regular + zksync) +yarn build # full build (tsc + hardhat compile + copy artifacts) +yarn test # compile + run all tests +npx hardhat test # run a specific test file +yarn lint # ESLint + Prettier + Solhint check +yarn prettier # auto-format code +yarn docgen # generate contract docs +``` + +### Fork Tests + +```bash +# Requires FORK=true + FORKED_NETWORK + ARCHIVE_NODE_ in .env +FORK=true FORKED_NETWORK=bscmainnet npx hardhat test test/fork/.ts +``` + +### Deploying + +```bash +yarn deploy:testnet # deploy to BSC testnet +yarn configure:testnet # configure on BSC testnet +npx hardhat --network deploy --tags # targeted deploy +yarn verify # Etherscan verification +``` + +--- + +## File Structure + +``` +contracts/ + ├── interfaces/ # All contract interfaces + ├── lib/ # Shared libraries + ├── oracles/ # Individual oracle implementations + │ └── common/ # Shared oracle base contracts + ├── test/ # Test-only contracts (mocks) + ├── DeviationBoundedOracle.sol + ├── ReferenceOracle.sol + └── ResilientOracle.sol + +test/ + ├── fork/ # Fork-based integration tests (FORK=true) + ├── utils/ # Test helpers + └── .ts # Unit tests per oracle (mirrors contracts/oracles/) + +deploy/ # Hardhat-deploy scripts (numbered, run in order) +deployments/ # Hardhat-deploy artifacts per network (git-tracked) +networks/ # Network-specific deployment addresses (mainnet.json) +helpers/ # TypeScript deployment helpers +artifacts/ # Compiled ABIs & artifacts (NOT git-tracked) +``` + +--- + +## Tests + +If tests exist and code changes are made: make the code change first, then immediately ask the user if you should update the tests before touching them. + +--- + +## Architecture + +### Oracle System + +The core is `ResilientOracle.sol` — aggregates prices from up to 3 configured sources (MAIN, PIVOT, FALLBACK) per asset with `BoundValidator` deviation checks. Individual oracle contracts fetch prices from external sources (Chainlink, Binance, staking protocols, etc.). + +### Key Contracts + +- **`ResilientOracle.sol`** — Main entry point; routes price requests, applies bound validation +- **`BoundValidator.sol`** — Validates price deviation ratio between two oracle sources +- **`DeviationBoundedOracle.sol`** — Wraps an oracle and reverts if price deviates beyond threshold vs a reference +- **`ReferenceOracle.sol`** — Simple oracle wrapper for reference price comparison +- **`contracts/oracles/`** — All individual oracle implementations (Chainlink, Binance, OneJump, ERC4626, Pendle, LST/LRT oracles, etc.) + +All oracle contracts implement `OracleInterface` (`getPrice(address asset)`). + +### Supported Networks + +Mainnets: `bscmainnet`, `ethereum`, `arbitrumone`, `opmainnet`, `opbnbmainnet`, `zksyncmainnet`, `basemainnet`, `unichainmainnet` + +Testnets: `bsctestnet`, `sepolia`, `arbitrumsepolia`, `opsepolia`, `opbnbtestnet`, `zksyncsepolia`, `basesepolia`, `unichainsepolia` + +ZkSync uses a separate config: `hardhat.config.zksync.ts` + +--- + +## Environment + +Requires archive node URLs in `.env` for fork tests (see `.env.example`): + +``` +ARCHIVE_NODE_bscmainnet=https://... +ARCHIVE_NODE_ethereum=https://... +``` + +--- + +## Conventions + +- Solidity `^0.8.25`, OpenZeppelin upgradeable contracts (UUPS pattern) +- Tests use `@defi-wonderland/smock` for mocking and `loadFixture` for snapshot isolation +- Commit messages follow conventional commits (enforced by commitlint + husky) +- Prettier: 120 char width, double quotes (single quotes for Solidity), sorted imports +- Solhint enforces Solidity style (`.solhint.json`) +- Deploy scripts are numbered and tagged — use `--tags` to deploy selectively + +--- + +## Venus Source Code + +- **Remote**: https://github.com/VenusProtocol (use `gh` for CLI access) +- **Deployed addresses**: [venus-protocol-documentation](https://github.com/VenusProtocol/venus-protocol-documentation) +- **Related repos**: `venus-protocol`, `isolated-pools`, `vips`, `governance-contracts` diff --git a/audits/183_deviationBoundedOracle_hashdit_20260417.pdf b/audits/183_deviationBoundedOracle_hashdit_20260417.pdf new file mode 100644 index 00000000..a74cd4d4 Binary files /dev/null and b/audits/183_deviationBoundedOracle_hashdit_20260417.pdf differ diff --git a/audits/184_deviationBoundedOracle_certik_20260423.pdf b/audits/184_deviationBoundedOracle_certik_20260423.pdf new file mode 100644 index 00000000..f6e0394f Binary files /dev/null and b/audits/184_deviationBoundedOracle_certik_20260423.pdf differ diff --git a/audits/187_deviationBoundedOracle_quantstamp_20260505.pdf b/audits/187_deviationBoundedOracle_quantstamp_20260505.pdf new file mode 100644 index 00000000..82a172ec Binary files /dev/null and b/audits/187_deviationBoundedOracle_quantstamp_20260505.pdf differ diff --git a/contracts/DeviationBoundedOracle.sol b/contracts/DeviationBoundedOracle.sol new file mode 100644 index 00000000..ef7ca7cd --- /dev/null +++ b/contracts/DeviationBoundedOracle.sol @@ -0,0 +1,929 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.25; + +import { VBep20Interface } from "./interfaces/VBep20Interface.sol"; +import { ResilientOracleInterface } from "./interfaces/OracleInterface.sol"; +import { IDeviationBoundedOracle } from "./interfaces/IDeviationBoundedOracle.sol"; +import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol"; +import { EXP_SCALE } from "@venusprotocol/solidity-utilities/contracts/constants.sol"; +import { ensureNonzeroAddress, ensureNonzeroValue } from "@venusprotocol/solidity-utilities/contracts/validators.sol"; +import { Transient } from "./lib/Transient.sol"; + +/** + * @title DeviationBoundedOracle + * @author Venus + * @notice The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations. + * + * It maintains a per-market rolling min/max price window. When the current spot price deviates + * significantly from the window bounds, protection mode activates automatically and conservative + * pricing kicks in: + * - Collateral is valued at min(spot, windowMin) — caps collateral value at recent window low + * - Debt is valued at max(spot, windowMax) — floors debt value at recent window high + * + * This protects against instantaneous or short-duration price manipulation attacks on low-liquidity + * collateral tokens. Sustained attacks beyond the window period are expected to be handled by + * off-chain monitoring systems. + * + * The oracle exposes both view and non-view price functions. The non-view variants update the + * price window and trigger protection. The view variants read stored state only. A transient + * price cache avoids redundant ResilientOracle calls within the same transaction when + * updateProtectionState is called before the view price reads. + */ +contract DeviationBoundedOracle is AccessControlledV8, IDeviationBoundedOracle { + /// @notice Minimum allowed threshold value (5%) to account for keeper deadband + uint256 public constant MIN_THRESHOLD = 5e16; + + /// @notice Maximum allowed threshold value (50%) + uint256 public constant MAX_THRESHOLD = 50e16; + + /// @notice Keeper deadband threshold (5%) — min/max corrections below this are suppressed + uint256 public constant KEEPER_DEADBAND = 5e16; + + /// @notice Resilient Oracle used to fetch spot prices + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + ResilientOracleInterface public immutable RESILIENT_ORACLE; + + /// @notice Native market address + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address public immutable nativeMarket; + + /// @notice VAI address + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address public immutable vai; + + /// @notice Transient storage slot for caching final collateral prices within a transaction + /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache + /// keccak256(abi.encode(uint256(keccak256("venus-protocol/oracle/DeviationBoundedOracle/collateralCache")) - 1)) + /// & ~bytes32(uint256(0xff)) + bytes32 public constant COLLATERAL_PRICE_CACHE_SLOT = + 0x7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf7000; + + /// @notice Transient storage slot for caching final debt prices within a transaction + /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache + /// keccak256(abi.encode(uint256(keccak256("venus-protocol/oracle/DeviationBoundedOracle/debtCache")) - 1)) + /// & ~bytes32(uint256(0xff)) + bytes32 public constant DEBT_PRICE_CACHE_SLOT = 0x84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb8970600; + + /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc) + /// and can serve as any underlying asset of a market that supports native tokens + address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB; + + /// @notice Per-asset protection state + mapping(address => MarketProtectionState) public assetProtectionConfig; + + /// @notice Append-only array of all assets ever initialized, used for enumeration + address[] public allAssets; + + /// @notice Storage gap for upgrades + uint256[48] private __gap; + + /** + * @notice Constructor for the implementation contract. Sets immutable variables. + * @param _resilientOracle Address of the ResilientOracle contract + * @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address) + * @param vaiAddress The address of the VAI token, or address(0) if VAI is not deployed on the chain. + * @custom:oz-upgrades-unsafe-allow constructor + */ + constructor(ResilientOracleInterface _resilientOracle, address nativeMarketAddress, address vaiAddress) { + ensureNonzeroAddress(address(_resilientOracle)); + ensureNonzeroAddress(nativeMarketAddress); + RESILIENT_ORACLE = _resilientOracle; + nativeMarket = nativeMarketAddress; + vai = vaiAddress; + _disableInitializers(); + } + + /** + * @notice Initializes the contract admin + * @param accessControlManager_ Address of the access control manager contract + */ + function initialize(address accessControlManager_) external initializer { + __AccessControlled_init(accessControlManager_); + } + + // ----- Non-view price functions (update window + trigger protection) ----- + + /** + * @notice Gets the bounded collateral price for a given vToken, updating protection state + * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger, + * and returns the conservative (lower) price when protection is active. + * Used by keepers or direct callers who want atomic update + read. + * @param vToken vToken address + * @return collateralPrice The bounded collateral price + * @custom:event MinPriceUpdated if a new window minimum is recorded + * @custom:event MaxPriceUpdated if a new window maximum is recorded + * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold + */ + function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice) { + (collateralPrice, ) = _updateAndGetBoundedPrices(vToken); + } + + /** + * @notice Gets the bounded debt price for a given vToken, updating protection state + * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger, + * and returns the conservative (higher) price when protection is active. + * Used by keepers or direct callers who want atomic update + read. + * @param vToken vToken address + * @return debtPrice The bounded debt price + * @custom:event MinPriceUpdated if a new window minimum is recorded + * @custom:event MaxPriceUpdated if a new window maximum is recorded + * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold + */ + function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice) { + (, debtPrice) = _updateAndGetBoundedPrices(vToken); + } + + /** + * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state + * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger, + * and returns both conservative prices in a single call. + * @param vToken vToken address + * @return collateralPrice The bounded collateral price + * @return debtPrice The bounded debt price + * @custom:event MinPriceUpdated if a new window minimum is recorded + * @custom:event MaxPriceUpdated if a new window maximum is recorded + * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold + */ + function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice) { + return _updateAndGetBoundedPrices(vToken); + } + + /** + * @notice Fetches the spot price, updates the protection window, and caches the resolved + * collateral and debt prices in transient storage for the duration of the transaction. + * @dev Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before + * liquidity calculations). Subsequent calls to getBoundedCollateralPriceView / + * getBoundedDebtPriceView within the same transaction will read from the transient cache + * instead of querying ResilientOracle again, keeping those functions as `view` and + * avoiding redundant oracle calls. + * The transient cache is only populated when the asset's `cachingEnabled` flag is `true`. + * When caching is disabled, view price reads fall through to live recomputation. + * Permissionless: anyone can call this, both for gas optimisation and to ensure every + * caller in the same transaction reads the correct, up-to-date bounded price. + * @param vToken vToken address + * @custom:event MinPriceUpdated if a new window minimum is recorded + * @custom:event MaxPriceUpdated if a new window maximum is recorded + * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold + */ + function updateProtectionState(address vToken) external { + _updateAndGetBoundedPrices(vToken); + } + + // ----- View price functions (read stored/cached state only) ----- + + /** + * @notice Gets the bounded collateral price for a given vToken (view variant) + * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true` + * (populated by a prior updateProtectionState call in the same transaction). Falls back + * to ResilientOracle on cache miss or when caching is disabled. + * Returns min(spot, windowMin) when protection is active, spot otherwise. + * @param vToken vToken address + * @return collateralPrice The bounded collateral price + */ + function getBoundedCollateralPriceView(address vToken) external view returns (uint256 collateralPrice) { + (collateralPrice, ) = _computeBoundedPrices(vToken); + } + + /** + * @notice Gets the bounded debt price for a given vToken (view variant) + * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true` + * (populated by a prior updateProtectionState call in the same transaction). Falls back + * to ResilientOracle on cache miss or when caching is disabled. + * Returns max(spot, windowMax) when protection is active, spot otherwise. + * @param vToken vToken address + * @return debtPrice The bounded debt price + */ + function getBoundedDebtPriceView(address vToken) external view returns (uint256 debtPrice) { + (, debtPrice) = _computeBoundedPrices(vToken); + } + + /** + * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant) + * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`; + * falls back to ResilientOracle on cache miss or when caching is disabled. + * @param vToken vToken address + * @return collateralPrice The bounded collateral price + * @return debtPrice The bounded debt price + */ + function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice) { + return _computeBoundedPrices(vToken); + } + + // ----- Keeper functions ----- + + /** + * @notice Updates the minimum price in the rolling window for a given asset + * @dev Called by the keeper to push corrected min values from the off-chain sliding window. + * Constraint: newMin must be at or below the current spot price. + * @param asset The underlying asset address + * @param newMin The new minimum price + * @custom:access Only authorized keeper addresses + * @custom:event MinPriceUpdated + */ + function updateMinPrice(address asset, uint128 newMin) external { + _checkAccessAllowed("updateMinPrice(address,uint128)"); + _validateAndUpdateBound(asset, newMin, PriceBoundType.MIN); + } + + /** + * @notice Updates the maximum price in the rolling window for a given asset + * @dev Called by the keeper to push corrected max values from the off-chain sliding window. + * Constraint: newMax must be at or above the current spot price. + * @param asset The underlying asset address + * @param newMax The new maximum price + * @custom:access Only authorized keeper addresses + * @custom:event MaxPriceUpdated + */ + function updateMaxPrice(address asset, uint128 newMax) external { + _checkAccessAllowed("updateMaxPrice(address,uint128)"); + _validateAndUpdateBound(asset, newMax, PriceBoundType.MAX); + } + + /** + * @notice Exits protection mode for a given asset + * @dev Called by the keeper/monitor after confirming price has normalised. + * Enforces two conditions on-chain: + * 1. Cooldown period has elapsed since the last trigger + * 2. Price range has converged below the exit threshold + * @param asset The underlying asset address + * @custom:access Only authorized monitor/keeper addresses + * @custom:error ProtectedPriceInactive if protection is not currently active + * @custom:error CooldownNotElapsed if cooldown period has not elapsed + * @custom:error PriceRangeNotConverged if window range is still above exit threshold + * @custom:event ProtectionModeExited + */ + function exitProtectionMode(address asset) external { + _checkAccessAllowed("exitProtectionMode(address)"); + _exitProtectionMode(asset); + } + + /** + * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check + * @dev Each item is processed in array order; any item revert rolls back the whole batch. + * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode. + * Empty `actions` is a no-op success. + * @param actions The list of keeper actions to apply + * @custom:access Only authorized keeper addresses + * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value + * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited + */ + function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external { + _checkAccessAllowed("syncPriceBoundsAndProtections((address,uint8,uint256)[])"); + uint256 len = actions.length; + for (uint256 i; i < len; ++i) { + KeeperActionItem calldata item = actions[i]; + if (item.action == KeeperAction.SetMinPrice) { + _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MIN); + } else if (item.action == KeeperAction.SetMaxPrice) { + _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MAX); + } else if (item.action == KeeperAction.ExitProtectionMode) { + _exitProtectionMode(item.asset); + } else { + revert InvalidKeeperAction(uint8(item.action)); + } + } + } + + // ----- Admin functions (governance-gated) ----- + + /** + * @notice Initializes protection for a new asset + * @param tokenConfig_ Token config input for the asset + * @custom:access Only Governance + * @custom:event ProtectionInitialized + * @custom:event BoundedPricingWhitelistUpdated + */ + function setTokenConfig(TokenConfigInput calldata tokenConfig_) external { + _checkAccessAllowed("setTokenConfig((address,uint64,uint256,uint256,bool,bool))"); + _setTokenConfig( + tokenConfig_.asset, + tokenConfig_.cooldownPeriod, + tokenConfig_.triggerThreshold, + tokenConfig_.resetThreshold, + tokenConfig_.enableBoundedPricing, + tokenConfig_.enableCaching + ); + } + + /** + * @notice Batch-initializes protection for multiple assets in a single transaction + * @param tokenConfigs_ Array of token config inputs, one per asset + * @custom:access Only Governance + * @custom:error InvalidArrayLength if the input array is empty + * @custom:event ProtectionInitialized for each asset + * @custom:event BoundedPricingWhitelistUpdated for each asset + */ + function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external { + _checkAccessAllowed("setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])"); + uint256 len = tokenConfigs_.length; + if (len == 0) revert InvalidArrayLength(); + + for (uint256 i; i < len; ++i) { + TokenConfigInput calldata tokenConfig = tokenConfigs_[i]; + _setTokenConfig( + tokenConfig.asset, + tokenConfig.cooldownPeriod, + tokenConfig.triggerThreshold, + tokenConfig.resetThreshold, + tokenConfig.enableBoundedPricing, + tokenConfig.enableCaching + ); + } + } + + /** + * @notice Sets the cooldown period for an asset + * @param asset The underlying asset address + * @param newCooldown The new cooldown period in seconds + * @custom:access Only Governance + * @custom:event CooldownPeriodSet + */ + function setCooldownPeriod(address asset, uint64 newCooldown) external { + _checkAccessAllowed("setCooldownPeriod(address,uint64)"); + ensureNonzeroAddress(asset); + ensureNonzeroValue(newCooldown); + + MarketProtectionState storage state = _ensureInitialized(asset); + emit CooldownPeriodSet(asset, state.cooldownPeriod, newCooldown); + state.cooldownPeriod = newCooldown; + } + + /** + * @notice Sets the trigger and reset thresholds for an asset + * @param asset The underlying asset address + * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold. + * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold. + * @custom:access Only Governance + * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5% + * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50% + * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold + * @custom:event TriggerThresholdSet if the trigger threshold changed + * @custom:event ResetThresholdSet if the reset threshold changed + */ + function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external { + _checkAccessAllowed("setThresholds(address,uint256,uint256)"); + ensureNonzeroAddress(asset); + ensureNonzeroValue(newTriggerThreshold); + ensureNonzeroValue(newResetThreshold); + if (newTriggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(newTriggerThreshold, MIN_THRESHOLD); + if (newTriggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(newTriggerThreshold, MAX_THRESHOLD); + if (newResetThreshold >= newTriggerThreshold) revert InvalidResetThreshold(newResetThreshold); + MarketProtectionState storage state = _ensureInitialized(asset); + + if (newTriggerThreshold != state.triggerThreshold) { + emit TriggerThresholdSet(asset, state.triggerThreshold, newTriggerThreshold); + state.triggerThreshold = uint128(newTriggerThreshold); + } + if (newResetThreshold != state.resetThreshold) { + emit ResetThresholdSet(asset, state.resetThreshold, newResetThreshold); + state.resetThreshold = uint128(newResetThreshold); + } + } + + /** + * @notice Sets whether an asset is enabled for bounded pricing + * @param asset The underlying asset address + * @param enabled Whether bounded pricing should be enabled for the asset + * @custom:access Only Governance + * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active + * @custom:event BoundedPricingWhitelistUpdated + */ + function setAssetBoundedPricingEnabled(address asset, bool enabled) external { + _checkAccessAllowed("setAssetBoundedPricingEnabled(address,bool)"); + ensureNonzeroAddress(asset); + + MarketProtectionState storage state = _ensureInitialized(asset); + + if (!enabled && state.currentlyUsingProtectedPrice) { + revert ProtectedPriceActive(asset); + } + + if (state.isBoundedPricingEnabled == enabled) return; + + // reset the window if re-enabling + if (enabled) { + uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset)); + _setMinPrice(state, asset, spotU128); + _setMaxPrice(state, asset, spotU128); + } + + state.isBoundedPricingEnabled = enabled; + emit BoundedPricingWhitelistUpdated(asset, enabled); + } + + /** + * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset + * @dev When disabled, each view/non-view price call recomputes bounded prices from the + * live spot instead of reading or writing the transient slots. The initial value is + * set via the `enableCaching` argument of `setTokenConfig`. + * @param asset The underlying asset address + * @param enabled Whether transient caching is enabled for this asset + * @custom:access Only Governance + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:event CachingEnabledUpdated + */ + function setCachingEnabled(address asset, bool enabled) external { + _checkAccessAllowed("setCachingEnabled(address,bool)"); + MarketProtectionState storage state = _ensureInitialized(asset); + emit CachingEnabledUpdated(asset, state.cachingEnabled, enabled); + state.cachingEnabled = enabled; + } + + // ----- View helpers ----- + + /** + * @notice Returns all asset addresses that have ever been initialized + * @return Array of all initialized asset addresses + */ + function getInitializedAssets() external view returns (address[] memory) { + return allAssets; + } + + /** + * @notice Checks if an asset is whitelisted for bounded pricing + * @param asset The underlying asset address + * @return True if the asset is whitelisted + */ + function isBoundedPricingEnabled(address asset) external view returns (bool) { + return assetProtectionConfig[asset].isBoundedPricingEnabled; + } + + /** + * @notice Checks if the asset is currently using the protected (bounded) price + * @param asset The underlying asset address + * @return True if the asset is currently using the protected price instead of spot + */ + function currentlyUsingProtectedPrice(address asset) external view returns (bool) { + return assetProtectionConfig[asset].currentlyUsingProtectedPrice; + } + + /** + * @notice Returns all currently whitelisted asset addresses + * @dev Iterates the append-only allAssets array and filters by isBoundedPricingEnabled. + * Gas-free for off-chain callers. + * @return result Array of whitelisted asset addresses + */ + function getAllBoundedPricingEnabledAssets() external view returns (address[] memory) { + uint256 len = allAssets.length; + address[] memory temp = new address[](len); + uint256 count; + for (uint256 i; i < len; ++i) { + if (assetProtectionConfig[allAssets[i]].isBoundedPricingEnabled) { + temp[count++] = allAssets[i]; + } + } + address[] memory result = new address[](count); + for (uint256 i; i < count; ++i) { + result[i] = temp[i]; + } + return result; + } + + /** + * @notice Checks if protection can be exited for an asset + * @dev Returns true when both conditions are met: + * 1. Cooldown period has elapsed since last trigger + * 2. Price range has converged below exit threshold + * @param asset The underlying asset address + * @return True if protection can be disabled + */ + function canExitProtection(address asset) external view returns (bool) { + MarketProtectionState storage state = assetProtectionConfig[asset]; + return + state.currentlyUsingProtectedPrice && + block.timestamp >= uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod) && + _computePriceBoundRatio(state.minPrice, state.maxPrice) < state.resetThreshold; + } + + /** + * @notice Batch-checks which assets' on-chain min/max have drifted beyond the deadband + * from the keeper's proposed window values + * @dev Allows the keeper to identify stale windows in a single call, avoiding N individual reads. + * Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE) + * @param assets Array of asset addresses to check + * @param proposedMins Keeper's off-chain window minimum prices + * @param proposedMaxs Keeper's off-chain window maximum prices + * @return needsMinUpdate Whether minPrice drift exceeds deadband for each asset + * @return needsMaxUpdate Whether maxPrice drift exceeds deadband for each asset + * @custom:error InvalidArrayLength if the input array lengths do not match + */ + function checkAndGetWindowDrift( + address[] calldata assets, + uint128[] calldata proposedMins, + uint128[] calldata proposedMaxs + ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate) { + uint256 len = assets.length; + if (len != proposedMins.length || len != proposedMaxs.length) revert InvalidArrayLength(); + + needsMinUpdate = new bool[](len); + needsMaxUpdate = new bool[](len); + + for (uint256 i; i < len; ++i) { + MarketProtectionState storage state = assetProtectionConfig[assets[i]]; + needsMinUpdate[i] = _exceedsCorrectionDeadband(state.minPrice, proposedMins[i]); + needsMaxUpdate[i] = _exceedsCorrectionDeadband(state.maxPrice, proposedMaxs[i]); + } + } + + // ----- Internal functions ----- + + /** + * @notice Initializes protection parameters and price window for a single asset + * @dev Fetches the current spot price from ResilientOracle to seed the initial min/max window, + * confirming the oracle is live for this asset before it is listed. Both bounds start at + * spot so the window expands naturally as prices move. Can only be called once per asset. + * @param asset The underlying asset address + * @param cooldownPeriod Minimum time protection stays active after last trigger + * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%. + * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold. + * @param enableBoundedPricing Whether to enable bounded pricing immediately upon initialization + * @param enableCaching Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset + * @custom:error ZeroAddressNotAllowed if asset is the zero address + * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero + * @custom:error MarketAlreadyInitialized if the asset has already been initialized + * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5% + * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50% + * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold + * @custom:error VAINotAllowed if asset is the VAI token + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 + */ + function _setTokenConfig( + address asset, + uint64 cooldownPeriod, + uint256 triggerThreshold, + uint256 resetThreshold, + bool enableBoundedPricing, + bool enableCaching + ) internal { + ensureNonzeroAddress(asset); + ensureNonzeroValue(cooldownPeriod); + ensureNonzeroValue(triggerThreshold); + ensureNonzeroValue(resetThreshold); + if (assetProtectionConfig[asset].asset != address(0)) revert MarketAlreadyInitialized(asset); + if (triggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(triggerThreshold, MIN_THRESHOLD); + if (triggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(triggerThreshold, MAX_THRESHOLD); + if (resetThreshold >= triggerThreshold) revert InvalidResetThreshold(resetThreshold); + if (asset == vai) revert VAINotAllowed(); + + uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset)); + + assetProtectionConfig[asset] = MarketProtectionState({ + minPrice: spotU128, + maxPrice: spotU128, + currentlyUsingProtectedPrice: false, + isBoundedPricingEnabled: enableBoundedPricing, + lastProtectionTriggeredAt: 0, + cooldownPeriod: cooldownPeriod, + asset: asset, + triggerThreshold: uint128(triggerThreshold), + resetThreshold: uint128(resetThreshold), + cachingEnabled: enableCaching + }); + + allAssets.push(asset); + + emit ProtectionInitialized(asset, spotU128, spotU128, cooldownPeriod, triggerThreshold); + emit BoundedPricingWhitelistUpdated(asset, enableBoundedPricing); + } + + /** + * @notice Validates and applies a keeper-provided min or max price update + * @param asset The underlying asset address + * @param newPrice The new price value to set + * @param boundType Whether this is a MIN or MAX bound update + * @custom:error ZeroPriceNotAllowed if newPrice is zero + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:error InvalidMinPrice if boundType is MIN and newPrice exceeds the current spot or is strictly above maxPrice + * @custom:error InvalidMaxPrice if boundType is MAX and newPrice is below the current spot or is strictly below minPrice + */ + function _validateAndUpdateBound(address asset, uint128 newPrice, PriceBoundType boundType) internal { + ensureNonzeroAddress(asset); + if (newPrice == 0) revert ZeroPriceNotAllowed(); + MarketProtectionState storage state = _ensureInitialized(asset); + + uint256 currentSpot = _fetchSpotPrice(asset); + if (boundType == PriceBoundType.MIN) { + if (newPrice > state.maxPrice || uint256(newPrice) > currentSpot) + revert InvalidMinPrice(asset, newPrice, currentSpot); + _setMinPrice(state, asset, newPrice); + } else if (boundType == PriceBoundType.MAX) { + if (newPrice < state.minPrice || uint256(newPrice) < currentSpot) + revert InvalidMaxPrice(asset, newPrice, currentSpot); + _setMaxPrice(state, asset, newPrice); + } + } + + /** + * @notice Clears protection for an asset once cooldown has elapsed and the window has converged + * @dev Shared body of `exitProtectionMode` and the ExitProtectionMode branch of `syncPriceBoundsAndProtections`. + * Callers are responsible for ACM gating before invoking this helper. + * @param asset The underlying asset address + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:error ProtectedPriceInactive if protection is not currently active + * @custom:error CooldownNotElapsed if cooldown period has not elapsed + * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold + */ + function _exitProtectionMode(address asset) internal { + ensureNonzeroAddress(asset); + MarketProtectionState storage state = _ensureInitialized(asset); + + if (!state.currentlyUsingProtectedPrice) revert ProtectedPriceInactive(asset); + + if (block.timestamp < uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod)) { + revert CooldownNotElapsed(asset, state.lastProtectionTriggeredAt, state.cooldownPeriod); + } + + uint256 rangeRatio = _computePriceBoundRatio(state.minPrice, state.maxPrice); + if (rangeRatio >= state.resetThreshold) { + revert PriceRangeNotConverged(asset, rangeRatio, state.resetThreshold); + } + + state.currentlyUsingProtectedPrice = false; + state.lastProtectionTriggeredAt = 0; + emit ProtectionModeExited(asset); + } + + /** + * @notice Shared non-view logic for all bounded price functions. + * Fetches spot, updates window, triggers protection if needed, and returns both bounded prices. + * @param vToken vToken address + * @return minPrice The bounded lower (collateral) price + * @return maxPrice The bounded upper (debt) price + */ + function _updateAndGetBoundedPrices(address vToken) internal returns (uint256 minPrice, uint256 maxPrice) { + address asset = _getUnderlyingAsset(vToken); + + // Early return if both prices were cached by a prior updateProtectionState call in this tx + (minPrice, maxPrice) = _getCachedPrices(asset); + if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice); + + // return early if failure from resilient oracle to prevent cold SLOAD + uint256 spot = _fetchSpotPrice(asset); + MarketProtectionState storage state = assetProtectionConfig[asset]; + if (!state.isBoundedPricingEnabled) { + _setCachedPrices(asset, spot, spot); + return (spot, spot); + } + (uint128 updatedMin, uint128 updatedMax, bool windowExpanded) = _expandPriceWindow(state, spot, asset); + bool protectionActive = _checkAndTriggerProtection(state, spot, asset, windowExpanded); + (minPrice, maxPrice) = _resolveBoundedPrices(protectionActive, spot, uint256(updatedMin), uint256(updatedMax)); + _setCachedPrices(asset, minPrice, maxPrice); + } + + /** + * @dev Expands the price window toward extremes if the spot price is a new min or max + * @param state The market protection state + * @param spot The current spot price + * @param asset The underlying asset address (for event emission) + */ + function _expandPriceWindow( + MarketProtectionState storage state, + uint256 spot, + address asset + ) internal returns (uint128, uint128, bool) { + uint128 spotU128 = _safeToUint128(spot); + uint128 currentMin = state.minPrice; + uint128 currentMax = state.maxPrice; + bool windowExpanded; + if (spotU128 < currentMin) { + _setMinPrice(state, asset, spotU128); + currentMin = spotU128; + windowExpanded = true; + } + if (spotU128 > currentMax) { + _setMaxPrice(state, asset, spotU128); + currentMax = spotU128; + windowExpanded = true; + } + return (currentMin, currentMax, windowExpanded); + } + + /** + * @dev Checks if the spot price has deviated beyond the threshold and triggers protection. + * `lastProtectionTriggeredAt` is reset only on the first trigger or when the price has made a + * genuine new extreme this update (windowExpanded == true). Recovery within the existing window + * keeps the cooldown ticking so `exitProtectionMode` remains reachable. + * @param state The market protection state + * @param spot The current spot price + * @param asset The underlying asset address (for event emission) + * @param windowExpanded True if `_expandPriceWindow` recorded a new low or new high this call + */ + function _checkAndTriggerProtection( + MarketProtectionState storage state, + uint256 spot, + address asset, + bool windowExpanded + ) internal returns (bool triggered) { + if (_exceedsDeviationThreshold(spot, state.minPrice, state.maxPrice, state.triggerThreshold)) { + bool enteringProtection = !state.currentlyUsingProtectedPrice; + if (enteringProtection || windowExpanded) { + state.lastProtectionTriggeredAt = uint64(block.timestamp); + } + if (enteringProtection) { + state.currentlyUsingProtectedPrice = true; + } + emit ProtectionTriggered(asset, spot, state.minPrice, state.maxPrice); + return true; + } + if (state.currentlyUsingProtectedPrice) return true; + } + + /** + * @notice Resolves the final bounded collateral and debt prices given a spot, window bounds, and protection flag. + * @dev When protection is active: collateral = min(spot, windowMin), debt = max(spot, windowMax). + * When protection is inactive: both return spot. + * @param protectionActive Whether the market protection window is currently active + * @param spot The current spot price + * @param windowMin The lower bound of the price window + * @param windowMax The upper bound of the price window + * @return minPrice The resolved lower-bound (collateral) price + * @return maxPrice The resolved upper-bound (debt) price + */ + function _resolveBoundedPrices( + bool protectionActive, + uint256 spot, + uint256 windowMin, + uint256 windowMax + ) internal pure returns (uint256, uint256) { + if (!protectionActive) return (spot, spot); + return (spot < windowMin ? spot : windowMin, spot > windowMax ? spot : windowMax); + } + + /** + * @notice Shared view logic for all bounded price view functions. + * Checks transient cache first for an early return; on miss, fetches from oracle + * and computes both prices without state mutations. + * @param vToken vToken address + * @return minPrice The bounded lower (collateral) price + * @return maxPrice The bounded upper (debt) price + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only) + */ + function _computeBoundedPrices(address vToken) internal view returns (uint256 minPrice, uint256 maxPrice) { + address asset = _getUnderlyingAsset(vToken); + + // Early return if both prices were cached by a prior updateProtectionState call in this tx + (minPrice, maxPrice) = _getCachedPrices(asset); + if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice); + + // Cache miss — fetch from oracle and compute without state mutations + uint256 spot = _fetchSpotPrice(asset); + MarketProtectionState storage state = assetProtectionConfig[asset]; + if (!state.isBoundedPricingEnabled) return (spot, spot); + + // Mirror _expandPriceWindow logic: compute what the window would be after expansion + uint128 spotU128 = _safeToUint128(spot); + uint128 windowMin128 = spot < uint256(state.minPrice) ? spotU128 : state.minPrice; + uint128 windowMax128 = spot > uint256(state.maxPrice) ? spotU128 : state.maxPrice; + + bool shouldProtect = state.currentlyUsingProtectedPrice || + _exceedsDeviationThreshold(spot, windowMin128, windowMax128, state.triggerThreshold); + + (minPrice, maxPrice) = _resolveBoundedPrices(shouldProtect, spot, uint256(windowMin128), uint256(windowMax128)); + } + + /** + * @dev Computes the relative spread between the price window bounds as a ratio scaled by EXP_SCALE. + * Formula: \((maxPrice - minPrice) / minPrice\), scaled by `EXP_SCALE`. + * Used to measure how much the window has converged -- compared against `resetThreshold` + * to determine whether the price window is tight enough to exit protection mode. + * @param minPrice The minimum price in the window + * @param maxPrice The maximum price in the window + * @return The scaled bound ratio \(((max - min) * EXP_SCALE) / min\) + */ + function _computePriceBoundRatio(uint128 minPrice, uint128 maxPrice) internal pure returns (uint256) { + uint256 range = uint256(maxPrice) - uint256(minPrice); + return (range * EXP_SCALE) / uint256(minPrice); + } + + /** + * @notice Checks whether the spot price has moved beyond the threshold relative to the + * opposite window bound — i.e. `spot > minPrice * (1 + threshold)` or + * `spot < maxPrice * (1 - threshold)`. + * @dev Pump detection: spot > minPrice * (1 + threshold) + * Crash detection: spot < maxPrice * (1 - threshold) + * @param spot The current spot price + * @param minPrice The minimum price in the window + * @param maxPrice The maximum price in the window + * @param threshold The deviation threshold (mantissa) + * @return True if deviation is triggered + */ + function _exceedsDeviationThreshold( + uint256 spot, + uint128 minPrice, + uint128 maxPrice, + uint256 threshold + ) internal pure returns (bool) { + uint256 upperBound = (uint256(minPrice) * (EXP_SCALE + threshold)) / EXP_SCALE; + uint256 lowerBound = (uint256(maxPrice) * (EXP_SCALE - threshold)) / EXP_SCALE; + return (spot > upperBound || spot < lowerBound); + } + + /** + * @dev Returns true if the relative drift between onChain and proposed exceeds KEEPER_DEADBAND + * @param currentPrice The current on-chain price + * @param proposedPrice The keeper's proposed price + * @return True if drift exceeds deadband + */ + function _exceedsCorrectionDeadband(uint128 currentPrice, uint128 proposedPrice) internal pure returns (bool) { + if (currentPrice == 0 || proposedPrice == 0) return false; + uint256 diff = currentPrice > proposedPrice + ? uint256(currentPrice - proposedPrice) + : uint256(proposedPrice - currentPrice); + return (diff * EXP_SCALE) / uint256(currentPrice) > KEEPER_DEADBAND; + } + + /** + * @dev Sets the minimum price in the window and emits MinPriceUpdated + * @param state The market protection state + * @param asset The underlying asset address (for event emission) + * @param newMin The new minimum price + */ + function _setMinPrice(MarketProtectionState storage state, address asset, uint128 newMin) internal { + emit MinPriceUpdated(asset, state.minPrice, newMin); + state.minPrice = newMin; + } + + /** + * @dev Sets the maximum price in the window and emits MaxPriceUpdated + * @param state The market protection state + * @param asset The underlying asset address (for event emission) + * @param newMax The new maximum price + */ + function _setMaxPrice(MarketProtectionState storage state, address asset, uint128 newMax) internal { + emit MaxPriceUpdated(asset, state.maxPrice, newMax); + state.maxPrice = newMax; + } + + /** + * @dev Writes both lower and upper bounded prices to transient storage. No-ops when the + * asset's `cachingEnabled` flag is `false`, so callers that disable caching always + * fall through to live recomputation on subsequent reads. + * @param asset The underlying asset address + * @param minPrice The resolved lower (collateral) price to cache + * @param maxPrice The resolved upper (debt) price to cache + */ + function _setCachedPrices(address asset, uint256 minPrice, uint256 maxPrice) internal { + if (!assetProtectionConfig[asset].cachingEnabled) return; + Transient.cachePrice(COLLATERAL_PRICE_CACHE_SLOT, asset, minPrice); + Transient.cachePrice(DEBT_PRICE_CACHE_SLOT, asset, maxPrice); + } + + /** + * @dev Reads a cached final price from transient storage. Returns `(0, 0)` when the + * asset's `cachingEnabled` flag is `false`, which callers already treat as a cache + * miss and handle via live recomputation. + * @param asset The underlying asset address + * @return minPrice The cached minimum price, or 0 on cache miss + * @return maxPrice The cached maximum price, or 0 on cache miss + */ + function _getCachedPrices(address asset) internal view returns (uint256 minPrice, uint256 maxPrice) { + if (!assetProtectionConfig[asset].cachingEnabled) return (0, 0); + minPrice = Transient.readCachedPrice(COLLATERAL_PRICE_CACHE_SLOT, asset); + maxPrice = Transient.readCachedPrice(DEBT_PRICE_CACHE_SLOT, asset); + } + + /** + * @dev This function returns the underlying asset of a vToken + * @param vToken vToken address + * @return asset underlying asset address + */ + function _getUnderlyingAsset(address vToken) private view returns (address asset) { + ensureNonzeroAddress(vToken); + if (vToken == nativeMarket) { + asset = NATIVE_TOKEN_ADDR; + } else if (vToken == vai) { + asset = vai; + } else { + asset = VBep20Interface(vToken).underlying(); + } + } + + /** + * @dev Reverts if the market has not been initialized via setTokenConfig + * @param asset The underlying asset address + * @return state The market protection state storage pointer + */ + function _ensureInitialized(address asset) internal view returns (MarketProtectionState storage state) { + state = assetProtectionConfig[asset]; + if (state.asset == address(0)) revert MarketNotInitialized(asset); + } + + /** + * @notice Fetches the current spot price for an asset from the ResilientOracle + * @param asset The underlying asset address + * @return The current spot price + */ + function _fetchSpotPrice(address asset) internal view returns (uint256) { + return RESILIENT_ORACLE.getPrice(asset); + } + + /** + * @dev Safely casts a uint256 to uint128, reverting on overflow + * @param value The value to cast + * @return The value as uint128 + */ + function _safeToUint128(uint256 value) internal pure returns (uint128) { + if (value > type(uint128).max) revert PriceExceedsUint128(value); + return uint128(value); + } +} diff --git a/contracts/interfaces/IDeviationBoundedOracle.sol b/contracts/interfaces/IDeviationBoundedOracle.sol new file mode 100644 index 00000000..f7cc444f --- /dev/null +++ b/contracts/interfaces/IDeviationBoundedOracle.sol @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.25; + +interface IDeviationBoundedOracle { + // --- Enums --- + + /// @notice Identifies whether a price bound is a minimum or maximum + enum PriceBoundType { + MIN, + MAX + } + + /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs + enum KeeperAction { + SetMinPrice, + SetMaxPrice, + ExitProtectionMode + } + + // --- Structs --- + + /// @notice Per-asset protection state tracking the min/max price window + struct MarketProtectionState { + /// @notice Lowest price observed in the current window (packed with maxPrice in one slot) + uint128 minPrice; + /// @notice Highest price observed in the current window + uint128 maxPrice; + /// @notice Whether protected price is currently being used + bool currentlyUsingProtectedPrice; + /// @notice Whether this market is whitelisted for bounded pricing + bool isBoundedPricingEnabled; + /// @notice Timestamp of the last protection trigger — reset on every trigger + uint64 lastProtectionTriggeredAt; + /// @notice Minimum time protection stays active after last trigger + uint64 cooldownPeriod; + /// @notice The underlying asset address, used to verify initialization + address asset; + /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold + uint128 triggerThreshold; + /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled + uint128 resetThreshold; + /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset + bool cachingEnabled; + } + + /// @notice One item in an syncPriceBoundsAndProtections payload + /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode + struct KeeperActionItem { + address asset; + KeeperAction action; + uint256 value; + } + + /// @notice One item in a setTokenConfigs payload + struct TokenConfigInput { + /// @notice The underlying asset address + address asset; + /// @notice Minimum time protection stays active after the last trigger (seconds) + uint64 cooldownPeriod; + /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%. + uint256 triggerThreshold; + /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold. + uint256 resetThreshold; + /// @notice Whether to enable bounded pricing immediately upon initialization + bool enableBoundedPricing; + /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset + bool enableCaching; + } + + // --- Events --- + + /// @notice Emitted when protection is initialized for an asset + event ProtectionInitialized( + address indexed asset, + uint128 minPrice, + uint128 maxPrice, + uint64 cooldownPeriod, + uint256 triggerThreshold + ); + + /// @notice Emitted when protection mode is triggered for an asset + event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice); + + /// @notice Emitted when protection mode is disabled for an asset + event ProtectionModeExited(address indexed asset); + + /// @notice Emitted when the keeper updates the minimum price for an asset + event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin); + + /// @notice Emitted when the keeper updates the maximum price for an asset + event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax); + + /// @notice Emitted when the entry threshold is updated for an asset + event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold); + + /// @notice Emitted when the exit threshold is updated for an asset + event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold); + + /// @notice Emitted when the cooldown period is updated for an asset + event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown); + + /// @notice Emitted when an asset's whitelist status changes + event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted); + + /// @notice Emitted when the per-asset transient caching flag is toggled + event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled); + + // --- Errors --- + + /// @notice Thrown when trying to use or update protection for an asset that has not been initialized + error MarketNotInitialized(address asset); + + /// @notice Thrown when trying to initialize an already initialized market + error MarketAlreadyInitialized(address asset); + + /// @notice Thrown when trying to disable protection that is not active + error ProtectedPriceInactive(address asset); + + /// @notice Thrown when trying to disable protection before cooldown has elapsed + error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod); + + /// @notice Thrown when trying to disable protection before price range has converged + error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold); + + /// @notice Thrown when keeper tries to set minPrice above current spot + error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot); + + /// @notice Thrown when keeper tries to set maxPrice below current spot + error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot); + + /// @notice Thrown when threshold is set below the minimum allowed value + error ThresholdBelowMinimum(uint256 threshold, uint256 minimum); + + /// @notice Thrown when threshold is set above the maximum allowed value + error ThresholdAboveMaximum(uint256 threshold, uint256 maximum); + + /// @notice Thrown when a price exceeds uint128 max + error PriceExceedsUint128(uint256 price); + + /// @notice Thrown when a zero price is provided where a non-zero price is required + error ZeroPriceNotAllowed(); + + /// @notice Thrown when trying to initialize protection for VAI + error VAINotAllowed(); + + /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active + error ProtectedPriceActive(address asset); + + /// @notice Thrown when the lengths of the arrays are not equal + error InvalidArrayLength(); + + /// @notice Thrown when the exit threshold is set at or above the trigger threshold + error InvalidResetThreshold(uint256 resetThreshold); + + /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value + error InvalidKeeperAction(uint8 action); + + // --- Non-view price functions (update window + trigger protection) --- + + /** + * @notice Gets the bounded collateral price for a given vToken, updating protection state + * @param vToken vToken address + * @return collateralPrice The bounded collateral price + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 + * @custom:event MinPriceUpdated if a new window minimum is recorded + * @custom:event MaxPriceUpdated if a new window maximum is recorded + * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold + */ + function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice); + + /** + * @notice Gets the bounded debt price for a given vToken, updating protection state + * @param vToken vToken address + * @return debtPrice The bounded debt price + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 + * @custom:event MinPriceUpdated if a new window minimum is recorded + * @custom:event MaxPriceUpdated if a new window maximum is recorded + * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold + */ + function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice); + + /** + * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state + * @param vToken vToken address + * @return collateralPrice The bounded collateral price + * @return debtPrice The bounded debt price + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 + * @custom:event MinPriceUpdated if a new window minimum is recorded + * @custom:event MaxPriceUpdated if a new window maximum is recorded + * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold + */ + function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice); + + // --- State update (call before view price reads to populate transient cache) --- + + /** + * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices + * @dev Called by PolicyFacet before liquidity calculations so subsequent view price + * reads in the same transaction are served from transient storage. The transient + * cache is only populated when the asset's `cachingEnabled` flag is `true`. + * @param vToken vToken address + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 + * @custom:event MinPriceUpdated if a new window minimum is recorded + * @custom:event MaxPriceUpdated if a new window maximum is recorded + * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold + */ + function updateProtectionState(address vToken) external; + + // --- View price functions (read stored/cached state only) --- + + /** + * @notice Gets the bounded collateral price for a given vToken (view variant) + * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`; + * falls back to ResilientOracle on cache miss or when caching is disabled. + * @param vToken vToken address + * @return price The bounded collateral price + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only) + */ + function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price); + + /** + * @notice Gets the bounded debt price for a given vToken (view variant) + * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`; + * falls back to ResilientOracle on cache miss or when caching is disabled. + * @param vToken vToken address + * @return price The bounded debt price + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only) + */ + function getBoundedDebtPriceView(address vToken) external view returns (uint256 price); + + /** + * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant) + * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`; + * falls back to ResilientOracle on cache miss or when caching is disabled. + * @param vToken vToken address + * @return collateralPrice The bounded collateral price + * @return debtPrice The bounded debt price + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only) + */ + function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice); + + // --- Keeper functions --- + + /** + * @notice Updates the minimum price in the rolling window for a given asset + * @param asset The underlying asset address + * @param newMin The new minimum price; must be at or below the current spot and below maxPrice + * @custom:access Only authorized keeper addresses + * @custom:error ZeroPriceNotAllowed if newMin is zero + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice + * @custom:event MinPriceUpdated + */ + function updateMinPrice(address asset, uint128 newMin) external; + + /** + * @notice Updates the maximum price in the rolling window for a given asset + * @param asset The underlying asset address + * @param newMax The new maximum price; must be at or above the current spot and above minPrice + * @custom:access Only authorized keeper addresses + * @custom:error ZeroPriceNotAllowed if newMax is zero + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice + * @custom:event MaxPriceUpdated + */ + function updateMaxPrice(address asset, uint128 newMax) external; + + /** + * @notice Exits protection mode for a given asset once conditions are met + * @param asset The underlying asset address + * @custom:access Only authorized monitor/keeper addresses + * @custom:error ProtectedPriceInactive if protection is not currently active + * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger + * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold + * @custom:event ProtectionModeExited + */ + function exitProtectionMode(address asset) external; + + /** + * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check + * @dev Each item is processed in array order; any item revert rolls back the whole batch. + * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode. + * Empty `actions` is a no-op success. + * @param actions The list of keeper actions to apply + * @custom:access Only authorized keeper addresses + * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value + * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128 + * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero + * @custom:error MarketNotInitialized if any referenced asset has not been initialized + * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints + * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints + * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active + * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed + * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence + * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited + */ + function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external; + + // --- Admin functions (governance-gated) --- + + /** + * @notice Initializes protection parameters for a new asset + * @dev Seeds the initial min/max window from the current ResilientOracle spot price, + * confirming the oracle is live for this asset before it is listed. + * @param tokenConfig_ Token config input for the asset + * @custom:access Only Governance + * @custom:error ZeroAddressNotAllowed if asset is the zero address + * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero + * @custom:error MarketAlreadyInitialized if the asset has already been initialized + * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5% + * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50% + * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold + * @custom:error VAINotAllowed if asset is the VAI token + * @custom:error PriceExceedsUint128 if the spot price overflows uint128 + * @custom:event ProtectionInitialized + * @custom:event BoundedPricingWhitelistUpdated + */ + function setTokenConfig(TokenConfigInput calldata tokenConfig_) external; + + /** + * @notice Batch-initializes protection parameters for multiple assets in a single transaction + * @param tokenConfigs_ Array of token config inputs, one per asset + * @custom:access Only Governance + * @custom:error InvalidArrayLength if the input array is empty + * @custom:error ZeroAddressNotAllowed if any asset is the zero address + * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero + * @custom:error MarketAlreadyInitialized if any asset has already been initialized + * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5% + * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50% + * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold + * @custom:error VAINotAllowed if any asset is the VAI token + * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128 + * @custom:event ProtectionInitialized for each asset + * @custom:event BoundedPricingWhitelistUpdated for each asset + */ + function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external; + + /** + * @notice Sets the cooldown period for an asset + * @param asset The underlying asset address + * @param newCooldown The new cooldown period in seconds; must be non-zero + * @custom:access Only Governance + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:event CooldownPeriodSet + */ + function setCooldownPeriod(address asset, uint64 newCooldown) external; + + /** + * @notice Sets the trigger and reset thresholds for an asset + * @param asset The underlying asset address + * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold. + * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold. + * @custom:access Only Governance + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5% + * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50% + * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold + * @custom:event TriggerThresholdSet if the trigger threshold changed + * @custom:event ResetThresholdSet if the reset threshold changed + */ + function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external; + + /** + * @notice Sets whether bounded pricing is enabled for an asset + * @param asset The underlying asset address + * @param enabled Whether bounded pricing should be enabled for the asset + * @custom:access Only Governance + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active + * @custom:event BoundedPricingWhitelistUpdated + */ + function setAssetBoundedPricingEnabled(address asset, bool enabled) external; + + /** + * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset + * @dev When disabled, each view/non-view price call recomputes bounded prices from the + * live spot instead of reading or writing the transient slots. The initial value is + * set via the `enableCaching` argument of `setTokenConfig`. + * @param asset The underlying asset address + * @param enabled Whether transient caching is enabled for this asset + * @custom:access Only Governance + * @custom:error MarketNotInitialized if the asset has not been initialized + * @custom:event CachingEnabledUpdated + */ + function setCachingEnabled(address asset, bool enabled) external; + + // --- View helpers --- + + /** + * @notice Returns the full protection state for an asset + * @param asset The underlying asset address + * @return minPrice Lowest price observed in the current window + * @return maxPrice Highest price observed in the current window + * @return currentlyUsingProtectedPrice Whether protected price is currently active + * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing + * @return lastProtectionTriggeredAt Timestamp of the last protection trigger + * @return cooldownPeriod Minimum time protection stays active after last trigger + * @return assetAddr The underlying asset address stored in the struct + * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection + * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled + * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset + */ + function assetProtectionConfig( + address asset + ) + external + view + returns ( + uint128 minPrice, + uint128 maxPrice, + bool currentlyUsingProtectedPrice, + bool isBoundedPricingEnabled, + uint64 lastProtectionTriggeredAt, + uint64 cooldownPeriod, + address assetAddr, + uint128 triggerThreshold, + uint128 resetThreshold, + bool cachingEnabled + ); + + /** + * @notice Checks if an asset is whitelisted for bounded pricing + * @param asset The underlying asset address + * @return True if the asset is whitelisted + */ + function isBoundedPricingEnabled(address asset) external view returns (bool); + + /** + * @notice Checks if the asset is currently using the protected (bounded) price + * @param asset The underlying asset address + * @return True if the asset is currently using the protected price instead of spot + */ + function currentlyUsingProtectedPrice(address asset) external view returns (bool); + + /** + * @notice Checks if protection can be exited for a given asset + * @param asset The underlying asset address + * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold + */ + function canExitProtection(address asset) external view returns (bool); + + /** + * @notice Returns the initialized asset at the given index (auto-generated array getter) + * @param index Array index + * @return The asset address at the given index + */ + function allAssets(uint256 index) external view returns (address); + + /** + * @notice Returns all currently whitelisted asset addresses + * @return result Array of whitelisted asset addresses + */ + function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result); + + /** + * @notice Returns all asset addresses that have ever been initialized + * @return Array of all initialized asset addresses + */ + function getInitializedAssets() external view returns (address[] memory); + + /** + * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband + * @param assets Array of asset addresses to check + * @param proposedMins Keeper's proposed window minimum prices + * @param proposedMaxs Keeper's proposed window maximum prices + * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset + * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset + * @custom:error InvalidArrayLength if the input array lengths do not match + */ + function checkAndGetWindowDrift( + address[] calldata assets, + uint128[] calldata proposedMins, + uint128[] calldata proposedMaxs + ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate); +} diff --git a/contracts/test/DeviationBoundedOracleCaller.sol b/contracts/test/DeviationBoundedOracleCaller.sol new file mode 100644 index 00000000..691032fb --- /dev/null +++ b/contracts/test/DeviationBoundedOracleCaller.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity 0.8.25; + +import { IDeviationBoundedOracle } from "../interfaces/IDeviationBoundedOracle.sol"; + +/// @notice Test helper that batches DeviationBoundedOracle calls in a single transaction +/// so transient storage (tstore/tload) cache can be tested. +contract DeviationBoundedOracleCaller { + IDeviationBoundedOracle public immutable oracle; + + constructor(address _oracle) { + oracle = IDeviationBoundedOracle(_oracle); + } + + function updateAndGetCollateralPrice(address vToken) external returns (uint256) { + oracle.updateProtectionState(vToken); + return oracle.getBoundedCollateralPriceView(vToken); + } + + function updateAndGetDebtPrice(address vToken) external returns (uint256) { + oracle.updateProtectionState(vToken); + return oracle.getBoundedDebtPriceView(vToken); + } + + function updateAndGetBothPrices(address vToken) external returns (uint256 collateral, uint256 debt) { + oracle.updateProtectionState(vToken); + collateral = oracle.getBoundedCollateralPriceView(vToken); + debt = oracle.getBoundedDebtPriceView(vToken); + } + + function updateThenNonViewCollateral(address vToken) external returns (uint256) { + oracle.updateProtectionState(vToken); + return oracle.getBoundedCollateralPrice(vToken); + } + + function twoConsecutiveNonViewCollateral(address vToken) external returns (uint256 first, uint256 second) { + first = oracle.getBoundedCollateralPrice(vToken); + second = oracle.getBoundedCollateralPrice(vToken); + } + + /// @notice Non-view wrapper so smock records oracle calls made by the view functions. + function getViewPricesWithoutUpdateNonView(address vToken) external returns (uint256 collateral, uint256 debt) { + collateral = oracle.getBoundedCollateralPriceView(vToken); + debt = oracle.getBoundedDebtPriceView(vToken); + } + + /// @notice Calls updateProtectionState on vTokenA, then getBoundedCollateralPriceView on vTokenB. + function updateAViewB(address vTokenA, address vTokenB) external returns (uint256) { + oracle.updateProtectionState(vTokenA); + return oracle.getBoundedCollateralPriceView(vTokenB); + } + + function getViewPricesWithoutUpdate(address vToken) external view returns (uint256 collateral, uint256 debt) { + collateral = oracle.getBoundedCollateralPriceView(vToken); + debt = oracle.getBoundedDebtPriceView(vToken); + } +} diff --git a/deploy/28-deploy-deviation-bounded-oracle.ts b/deploy/28-deploy-deviation-bounded-oracle.ts new file mode 100644 index 00000000..d07a3ff6 --- /dev/null +++ b/deploy/28-deploy-deviation-bounded-oracle.ts @@ -0,0 +1,56 @@ +import hre, { ethers } from "hardhat"; +import { DeployFunction } from "hardhat-deploy/dist/types"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +import { ADDRESSES } from "../helpers/deploymentConfig"; + +const func: DeployFunction = async function ({ getNamedAccounts, deployments, network }: HardhatRuntimeEnvironment) { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + // Fallback placeholder addresses are used on the hardhat network where ADDRESSES is not defined + const accessControlManagerAddress = ADDRESSES[network.name]?.acm || "0x0000000000000000000000000000000000000001"; + + const proxyOwnerAddress = network.live ? ADDRESSES[network.name].timelock : deployer; + const vbnbAddress = ADDRESSES[network.name]?.vBNBAddress || "0x0000000000000000000000000000000000000001"; + const vaiAddress = ADDRESSES[network.name]?.VAIAddress || ethers.constants.AddressZero; + const timelock = ADDRESSES[network.name]?.timelock || "0x0000000000000000000000000000000000000001"; + + const resilientOracle = await hre.ethers.getContract("ResilientOracle"); + + const defaultProxyAdmin = await hre.artifacts.readArtifact( + "hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol:ProxyAdmin", + ); + + await deploy("DeviationBoundedOracle", { + from: deployer, + log: true, + deterministicDeployment: false, + skipIfAlreadyDeployed: true, + args: [resilientOracle.address, vbnbAddress, vaiAddress], + proxy: { + owner: proxyOwnerAddress, + proxyContract: "OptimizedTransparentUpgradeableProxy", + execute: { + methodName: "initialize", + args: [accessControlManagerAddress], + }, + viaAdminContract: { + name: "DefaultProxyAdmin", + artifact: defaultProxyAdmin, + }, + }, + }); + + const deviationBoundedOracle = await hre.ethers.getContract("DeviationBoundedOracle"); + const owner = await deviationBoundedOracle.owner(); + + if (owner === deployer) { + await deviationBoundedOracle.transferOwnership(timelock); + console.log(`Ownership of DeviationBoundedOracle transferred from deployer to Timelock (${timelock})`); + } +}; + +export default func; +func.tags = ["deploy-deviation-bounded-oracle"]; +func.dependencies = ["deploy"]; diff --git a/deployments/bscmainnet.json b/deployments/bscmainnet.json index 95171c0d..ac93b97b 100644 --- a/deployments/bscmainnet.json +++ b/deployments/bscmainnet.json @@ -4653,6 +4653,3006 @@ } ] }, + "DeviationBoundedOracle": { + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidKeeperAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newEnabled", + "type": "bool" + } + ], + "name": "CachingEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setCachingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput", + "name": "tokenConfig_", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum IDeviationBoundedOracle.KeeperAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IDeviationBoundedOracle.KeeperActionItem[]", + "name": "actions", + "type": "tuple[]" + } + ], + "name": "syncPriceBoundsAndProtections", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } + ] + }, + "DeviationBoundedOracle_Implementation": { + "address": "0x16691f500541ca35bd63DD878B6D78728C9518AE", + "abi": [ + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "_resilientOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "nativeMarketAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaiAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidKeeperAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newEnabled", + "type": "bool" + } + ], + "name": "CachingEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setCachingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput", + "name": "tokenConfig_", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum IDeviationBoundedOracle.KeeperAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IDeviationBoundedOracle.KeeperActionItem[]", + "name": "actions", + "type": "tuple[]" + } + ], + "name": "syncPriceBoundsAndProtections", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ] + }, + "DeviationBoundedOracle_Proxy": { + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] + }, "PendleOracle-PT-SolvBTC.BBN-27MAR2025": { "address": "0xE11965a3513F537d91D73d9976FBe8c0969Bb252", "abi": [ diff --git a/deployments/bscmainnet/DeviationBoundedOracle.json b/deployments/bscmainnet/DeviationBoundedOracle.json new file mode 100644 index 00000000..c356168e --- /dev/null +++ b/deployments/bscmainnet/DeviationBoundedOracle.json @@ -0,0 +1,1618 @@ +{ + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidKeeperAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newEnabled", + "type": "bool" + } + ], + "name": "CachingEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setCachingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput", + "name": "tokenConfig_", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum IDeviationBoundedOracle.KeeperAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IDeviationBoundedOracle.KeeperActionItem[]", + "name": "actions", + "type": "tuple[]" + } + ], + "name": "syncPriceBoundsAndProtections", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } + ], + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "receipt": { + "to": null, + "from": "0x9b0A3EAE7f174937d31745B710BbeA68e9D1BEf7", + "contractAddress": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "transactionIndex": 67, + "gasUsed": "589361", + "logsBloom": "0x0000000000000000000000004000000040000000000000020080000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000008000000000a000001000000000000000000000000000100000000020000000000000000000800000000800000000000010800000000400000000000000000000000000000000000000000000080000000000000800000000000000000000000000000000400000000000000800000000000000000000000000020000000000000000000040000000000000400000000000000000020000000000000000000000000008000000000000800000000000000000000000000", + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1", + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "logs": [ + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x00000000000000000000000016691f500541ca35bd63dd878b6d78728c9518ae" + ], + "data": "0x", + "logIndex": 214, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + }, + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000009b0a3eae7f174937d31745b710bbea68e9d1bef7" + ], + "data": "0x", + "logIndex": 215, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + }, + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": ["0x66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa0"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004788629abc6cfca10f9f969efdeaa1cf70c23555", + "logIndex": 216, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + }, + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 217, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + }, + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bb765b741a5f3c2a338369dab539385534e3343", + "logIndex": 218, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + } + ], + "blockNumber": 95554965, + "cumulativeGasUsed": "8547839", + "status": 1, + "byzantium": true + }, + "args": [ + "0x16691f500541ca35bd63DD878B6D78728C9518AE", + "0x1BB765b741A5f3C2A338369DAb539385534E3343", + "0xc4d66de80000000000000000000000004788629abc6cfca10f9f969efdeaa1cf70c23555" + ], + "numDeployments": 1, + "solcInputHash": "b08018e2eab5f4ca5e3ba0c1798444d8", + "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"implementation_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"details\":\"This contract implements a proxy that is upgradeable by an admin. 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.\",\"events\":{\"AdminChanged(address,address)\":{\"details\":\"Emitted when the admin account has changed.\"},\"BeaconUpgraded(address)\":{\"details\":\"Emitted when the beacon is upgraded.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"admin()\":{\"details\":\"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`\"},\"constructor\":{\"details\":\"Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\"},\"implementation()\":{\"details\":\"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`\"},\"upgradeTo(address)\":{\"details\":\"Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\"},\"upgradeToAndCall(address,bytes)\":{\"details\":\"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}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol\":\"OptimizedTransparentUpgradeableProxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"hardhat-deploy/solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\\n * proxy whose upgrades are fully controlled by the current implementation.\\n */\\ninterface IERC1822Proxiable {\\n /**\\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\\n * address.\\n *\\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\\n * function revert if invoked through a proxy.\\n */\\n function proxiableUUID() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x93b4e21c931252739a1ec13ea31d3d35a5c068be3163ccab83e4d70c40355f03\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Proxy.sol\\\";\\nimport \\\"./ERC1967Upgrade.sol\\\";\\n\\n/**\\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\\n * implementation address that can be changed. This address is stored in storage in the location specified by\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\\n * implementation behind the proxy.\\n */\\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\\n /**\\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\\n *\\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\\n */\\n constructor(address _logic, bytes memory _data) payable {\\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.implementation\\\")) - 1));\\n _upgradeToAndCall(_logic, _data, false);\\n }\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _implementation() internal view virtual override returns (address impl) {\\n return ERC1967Upgrade._getImplementation();\\n }\\n}\\n\",\"keccak256\":\"0x6309f9f39dc6f4f45a24f296543867aa358e32946cd6b2874627a996d606b3a0\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../beacon/IBeacon.sol\\\";\\nimport \\\"../../interfaces/draft-IERC1822.sol\\\";\\nimport \\\"../../utils/Address.sol\\\";\\nimport \\\"../../utils/StorageSlot.sol\\\";\\n\\n/**\\n * @dev This abstract contract provides getters and event emitting update functions for\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\\n *\\n * _Available since v4.1._\\n *\\n * @custom:oz-upgrades-unsafe-allow delegatecall\\n */\\nabstract contract ERC1967Upgrade {\\n // This is the keccak-256 hash of \\\"eip1967.proxy.rollback\\\" subtracted by 1\\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\\n\\n /**\\n * @dev Storage slot with the address of the current implementation.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.implementation\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n\\n /**\\n * @dev Emitted when the implementation is upgraded.\\n */\\n event Upgraded(address indexed implementation);\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _getImplementation() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 implementation slot.\\n */\\n function _setImplementation(address newImplementation) private {\\n require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n }\\n\\n /**\\n * @dev Perform implementation upgrade\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeTo(address newImplementation) internal {\\n _setImplementation(newImplementation);\\n emit Upgraded(newImplementation);\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCall(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _upgradeTo(newImplementation);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(newImplementation, data);\\n }\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCallUUPS(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n // Upgrades from old implementations will perform a rollback test. This test requires the new\\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\\n // this special case will break upgrade paths from old UUPS implementation to new ones.\\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\\n _setImplementation(newImplementation);\\n } else {\\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\\n require(slot == _IMPLEMENTATION_SLOT, \\\"ERC1967Upgrade: unsupported proxiableUUID\\\");\\n } catch {\\n revert(\\\"ERC1967Upgrade: new implementation is not UUPS\\\");\\n }\\n _upgradeToAndCall(newImplementation, data, forceCall);\\n }\\n }\\n\\n /**\\n * @dev Storage slot with the admin of the contract.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.admin\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\\n\\n /**\\n * @dev Emitted when the admin account has changed.\\n */\\n event AdminChanged(address previousAdmin, address newAdmin);\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _getAdmin() internal view virtual returns (address) {\\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 admin slot.\\n */\\n function _setAdmin(address newAdmin) private {\\n require(newAdmin != address(0), \\\"ERC1967: new admin is the zero address\\\");\\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\\n }\\n\\n /**\\n * @dev Changes the admin of the proxy.\\n *\\n * Emits an {AdminChanged} event.\\n */\\n function _changeAdmin(address newAdmin) internal {\\n emit AdminChanged(_getAdmin(), newAdmin);\\n _setAdmin(newAdmin);\\n }\\n\\n /**\\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\\n */\\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\\n\\n /**\\n * @dev Emitted when the beacon is upgraded.\\n */\\n event BeaconUpgraded(address indexed beacon);\\n\\n /**\\n * @dev Returns the current beacon.\\n */\\n function _getBeacon() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new beacon in the EIP1967 beacon slot.\\n */\\n function _setBeacon(address newBeacon) private {\\n require(Address.isContract(newBeacon), \\\"ERC1967: new beacon is not a contract\\\");\\n require(Address.isContract(IBeacon(newBeacon).implementation()), \\\"ERC1967: beacon implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\\n }\\n\\n /**\\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\\n *\\n * Emits a {BeaconUpgraded} event.\\n */\\n function _upgradeBeaconToAndCall(\\n address newBeacon,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _setBeacon(newBeacon);\\n emit BeaconUpgraded(newBeacon);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x17668652127feebed0ce8d9431ef95ccc8c4292f03e3b8cf06c6ca16af396633\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\\n * be specified by overriding the virtual {_implementation} function.\\n *\\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\\n * different contract through the {_delegate} function.\\n *\\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\\n */\\nabstract contract Proxy {\\n /**\\n * @dev Delegates the current call to `implementation`.\\n *\\n * This function does not return to its internal call site, it will return directly to the external caller.\\n */\\n function _delegate(address implementation) internal virtual {\\n assembly {\\n // Copy msg.data. We take full control of memory in this inline assembly\\n // block because it will not return to Solidity code. We overwrite the\\n // Solidity scratch pad at memory position 0.\\n calldatacopy(0, 0, calldatasize())\\n\\n // Call the implementation.\\n // out and outsize are 0 because we don't know the size yet.\\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\\n\\n // Copy the returned data.\\n returndatacopy(0, 0, returndatasize())\\n\\n switch result\\n // delegatecall returns 0 on error.\\n case 0 {\\n revert(0, returndatasize())\\n }\\n default {\\n return(0, returndatasize())\\n }\\n }\\n }\\n\\n /**\\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\\n * and {_fallback} should delegate.\\n */\\n function _implementation() internal view virtual returns (address);\\n\\n /**\\n * @dev Delegates the current call to the address returned by `_implementation()`.\\n *\\n * This function does not return to its internall call site, it will return directly to the external caller.\\n */\\n function _fallback() internal virtual {\\n _beforeFallback();\\n _delegate(_implementation());\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\\n * function in the contract matches the call data.\\n */\\n fallback() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\\n * is empty.\\n */\\n receive() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\\n * call, or as part of the Solidity `fallback` or `receive` functions.\\n *\\n * If overriden should call `super._beforeFallback()`.\\n */\\n function _beforeFallback() internal virtual {}\\n}\\n\",\"keccak256\":\"0xd5d1fd16e9faff7fcb3a52e02a8d49156f42a38a03f07b5f1810c21c2149a8ab\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\\n */\\ninterface IBeacon {\\n /**\\n * @dev Must return an address that can be used as a delegate call target.\\n *\\n * {BeaconProxy} will check that this address is a contract.\\n */\\n function implementation() external view returns (address);\\n}\\n\",\"keccak256\":\"0xd50a3421ac379ccb1be435fa646d66a65c986b4924f0849839f08692f39dde61\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3777e696b62134e6177440dbe6e6601c0c156a443f57167194b67e75527439de\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/utils/StorageSlot.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for reading and writing primitive types to specific storage slots.\\n *\\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\\n * This library helps with reading and writing to such slots without the need for inline assembly.\\n *\\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\\n *\\n * Example usage to set ERC1967 implementation slot:\\n * ```\\n * contract ERC1967 {\\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n *\\n * function _getImplementation() internal view returns (address) {\\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n * }\\n *\\n * function _setImplementation(address newImplementation) internal {\\n * require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n * }\\n * }\\n * ```\\n *\\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\\n */\\nlibrary StorageSlot {\\n struct AddressSlot {\\n address value;\\n }\\n\\n struct BooleanSlot {\\n bool value;\\n }\\n\\n struct Bytes32Slot {\\n bytes32 value;\\n }\\n\\n struct Uint256Slot {\\n uint256 value;\\n }\\n\\n /**\\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\\n */\\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\\n */\\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\\n */\\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\\n */\\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfe1b7a9aa2a530a9e705b220e26cd584e2fbdc9602a3a1066032b12816b46aca\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\\\";\\n\\n/**\\n * @dev This contract implements a proxy that is upgradeable by an admin.\\n *\\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\\n * clashing], which can potentially be used in an attack, this contract uses the\\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\\n * things that go hand in hand:\\n *\\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\\n * that call matches one of the admin functions exposed by the proxy itself.\\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\\n * \\\"admin cannot fallback to proxy target\\\".\\n *\\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\\n * to sudden errors when trying to call a function from the proxy implementation.\\n *\\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\\n */\\ncontract OptimizedTransparentUpgradeableProxy is ERC1967Proxy {\\n address internal immutable _ADMIN;\\n\\n /**\\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\\n */\\n constructor(\\n address _logic,\\n address admin_,\\n bytes memory _data\\n ) payable ERC1967Proxy(_logic, _data) {\\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.admin\\\")) - 1));\\n _ADMIN = admin_;\\n\\n // still store it to work with EIP-1967\\n bytes32 slot = _ADMIN_SLOT;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(slot, admin_)\\n }\\n emit AdminChanged(address(0), admin_);\\n }\\n\\n /**\\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\\n */\\n modifier ifAdmin() {\\n if (msg.sender == _getAdmin()) {\\n _;\\n } else {\\n _fallback();\\n }\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\\n */\\n function admin() external ifAdmin returns (address admin_) {\\n admin_ = _getAdmin();\\n }\\n\\n /**\\n * @dev Returns the current implementation.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\\n */\\n function implementation() external ifAdmin returns (address implementation_) {\\n implementation_ = _implementation();\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\\n */\\n function upgradeTo(address newImplementation) external ifAdmin {\\n _upgradeToAndCall(newImplementation, bytes(\\\"\\\"), false);\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\\n * proxied contract.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\\n */\\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\\n _upgradeToAndCall(newImplementation, data, true);\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _admin() internal view virtual returns (address) {\\n return _getAdmin();\\n }\\n\\n /**\\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\\n */\\n function _beforeFallback() internal virtual override {\\n require(msg.sender != _getAdmin(), \\\"TransparentUpgradeableProxy: admin cannot fallback to proxy target\\\");\\n super._beforeFallback();\\n }\\n\\n function _getAdmin() internal view virtual override returns (address) {\\n return _ADMIN;\\n }\\n}\\n\",\"keccak256\":\"0xa30117644e27fa5b49e162aae2f62b36c1aca02f801b8c594d46e2024963a534\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60a0604052604051610c8f380380610c8f8339810160408190526100229161039f565b828161004f60017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd61046a565b5f80516020610c488339815191521461006a5761006a610489565b61007582825f610123565b506100a3905060017fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610461046a565b5f80516020610c28833981519152146100be576100be610489565b6001600160a01b03821660808190525f80516020610c28833981519152838155604080515f8152602081019390935290917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050506104e8565b61012c8361014e565b5f825111806101385750805b1561014957610147838361018d565b505b505050565b610157816101bb565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606101b28383604051806060016040528060278152602001610c686027913961025b565b90505b92915050565b6001600160a01b0381163b61022d5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b5f80516020610c4883398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6102c35760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610224565b5f80856001600160a01b0316856040516102dd919061049d565b5f60405180830381855af49150503d805f8114610315576040519150601f19603f3d011682016040523d82523d5f602084013e61031a565b606091505b50909250905061032b828286610337565b925050505b9392505050565b60608315610346575081610330565b8251156103565782518084602001fd5b8160405162461bcd60e51b815260040161022491906104b3565b80516001600160a01b0381168114610386575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f805f606084860312156103b1575f80fd5b6103ba84610370565b92506103c860208501610370565b60408501519092506001600160401b03808211156103e4575f80fd5b818601915086601f8301126103f7575f80fd5b8151818111156104095761040961038b565b604051601f8201601f19908116603f011681019083821181831017156104315761043161038b565b81604052828152896020848701011115610449575f80fd5b8260208601602083015e5f6020848301015280955050505050509250925092565b818103818111156101b557634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52600160045260245ffd5b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b6080516107066105225f395f818160eb0152818161013f015281816101bf0152818161020801528181610239015261025d01526107065ff3fe608060405260043610610042575f3560e01c80633659cfe6146100595780634f1ef286146100785780635c60da1b1461008b578063f851a440146100bb57610051565b366100515761004f6100cf565b005b61004f6100cf565b348015610064575f80fd5b5061004f6100733660046105c9565b6100e9565b61004f6100863660046105e2565b61013d565b348015610096575f80fd5b5061009f6101bc565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c6575f80fd5b5061009f610205565b6100d761025b565b6100e76100e2610309565b61033b565b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610135576101328160405180602001604052805f8152505f610359565b50565b6101326100cf565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101b4576101af8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610359915050565b505050565b6101af6100cf565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa576101f5610309565b905090565b6102026100cf565b90565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa57507f000000000000000000000000000000000000000000000000000000000000000090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036100e75760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101f57f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b365f80375f80365f845af43d5f803e808015610355573d5ff35b3d5ffd5b61036283610383565b5f8251118061036e5750805b156101af5761037d83836103c2565b50505050565b61038c816103ee565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103e783836040518060600160405280602781526020016106aa6027913961049c565b9392505050565b6001600160a01b0381163b61045b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610300565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6105045760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610300565b5f80856001600160a01b03168560405161051e919061065e565b5f60405180830381855af49150503d805f8114610556576040519150601f19603f3d011682016040523d82523d5f602084013e61055b565b606091505b509150915061056b828286610575565b9695505050505050565b606083156105845750816103e7565b8251156105945782518084602001fd5b8160405162461bcd60e51b81526004016103009190610674565b80356001600160a01b03811681146105c4575f80fd5b919050565b5f602082840312156105d9575f80fd5b6103e7826105ae565b5f805f604084860312156105f4575f80fd5b6105fd846105ae565b9250602084013567ffffffffffffffff80821115610619575f80fd5b818601915086601f83011261062c575f80fd5b81358181111561063a575f80fd5b87602082850101111561064b575f80fd5b6020830194508093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d447ec2a4b47962d9e959ca0ec6571d77747999259501e773b66fdcaef0e50bf64736f6c63430008190033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", + "deployedBytecode": "0x608060405260043610610042575f3560e01c80633659cfe6146100595780634f1ef286146100785780635c60da1b1461008b578063f851a440146100bb57610051565b366100515761004f6100cf565b005b61004f6100cf565b348015610064575f80fd5b5061004f6100733660046105c9565b6100e9565b61004f6100863660046105e2565b61013d565b348015610096575f80fd5b5061009f6101bc565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c6575f80fd5b5061009f610205565b6100d761025b565b6100e76100e2610309565b61033b565b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610135576101328160405180602001604052805f8152505f610359565b50565b6101326100cf565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101b4576101af8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610359915050565b505050565b6101af6100cf565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa576101f5610309565b905090565b6102026100cf565b90565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa57507f000000000000000000000000000000000000000000000000000000000000000090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036100e75760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101f57f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b365f80375f80365f845af43d5f803e808015610355573d5ff35b3d5ffd5b61036283610383565b5f8251118061036e5750805b156101af5761037d83836103c2565b50505050565b61038c816103ee565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103e783836040518060600160405280602781526020016106aa6027913961049c565b9392505050565b6001600160a01b0381163b61045b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610300565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6105045760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610300565b5f80856001600160a01b03168560405161051e919061065e565b5f60405180830381855af49150503d805f8114610556576040519150601f19603f3d011682016040523d82523d5f602084013e61055b565b606091505b509150915061056b828286610575565b9695505050505050565b606083156105845750816103e7565b8251156105945782518084602001fd5b8160405162461bcd60e51b81526004016103009190610674565b80356001600160a01b03811681146105c4575f80fd5b919050565b5f602082840312156105d9575f80fd5b6103e7826105ae565b5f805f604084860312156105f4575f80fd5b6105fd846105ae565b9250602084013567ffffffffffffffff80821115610619575f80fd5b818601915086601f83011261062c575f80fd5b81358181111561063a575f80fd5b87602082850101111561064b575f80fd5b6020830194508093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d447ec2a4b47962d9e959ca0ec6571d77747999259501e773b66fdcaef0e50bf64736f6c63430008190033", + "execute": { + "methodName": "initialize", + "args": ["0x4788629abc6cfca10f9f969efdeaa1cf70c23555"] + }, + "implementation": "0x16691f500541ca35bd63DD878B6D78728C9518AE", + "devdoc": { + "details": "This contract implements a proxy that is upgradeable by an admin. 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.", + "events": { + "AdminChanged(address,address)": { + "details": "Emitted when the admin account has changed." + }, + "BeaconUpgraded(address)": { + "details": "Emitted when the beacon is upgraded." + }, + "Upgraded(address)": { + "details": "Emitted when the implementation is upgraded." + } + }, + "kind": "dev", + "methods": { + "admin()": { + "details": "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`" + }, + "constructor": { + "details": "Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}." + }, + "implementation()": { + "details": "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`" + }, + "upgradeTo(address)": { + "details": "Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}." + }, + "upgradeToAndCall(address,bytes)": { + "details": "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}." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} diff --git a/deployments/bscmainnet/DeviationBoundedOracle_Implementation.json b/deployments/bscmainnet/DeviationBoundedOracle_Implementation.json new file mode 100644 index 00000000..878d59d8 --- /dev/null +++ b/deployments/bscmainnet/DeviationBoundedOracle_Implementation.json @@ -0,0 +1,2191 @@ +{ + "address": "0x16691f500541ca35bd63DD878B6D78728C9518AE", + "abi": [ + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "_resilientOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "nativeMarketAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaiAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidKeeperAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newEnabled", + "type": "bool" + } + ], + "name": "CachingEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setCachingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput", + "name": "tokenConfig_", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum IDeviationBoundedOracle.KeeperAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IDeviationBoundedOracle.KeeperActionItem[]", + "name": "actions", + "type": "tuple[]" + } + ], + "name": "syncPriceBoundsAndProtections", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0xf261ff1e3635bf22739e370300e0297474050662e8c24e9e147bae87a802c6ef", + "receipt": { + "to": null, + "from": "0x9b0A3EAE7f174937d31745B710BbeA68e9D1BEf7", + "contractAddress": "0x16691f500541ca35bd63DD878B6D78728C9518AE", + "transactionIndex": 33, + "gasUsed": "2869841", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x8bd2f130cc2adb37e0e1d307a2829e04e886f57a389769e2d4a54d17f9df13b0", + "transactionHash": "0xf261ff1e3635bf22739e370300e0297474050662e8c24e9e147bae87a802c6ef", + "logs": [ + { + "transactionIndex": 33, + "blockNumber": 95554959, + "transactionHash": "0xf261ff1e3635bf22739e370300e0297474050662e8c24e9e147bae87a802c6ef", + "address": "0x16691f500541ca35bd63DD878B6D78728C9518AE", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000ff", + "logIndex": 99, + "blockHash": "0x8bd2f130cc2adb37e0e1d307a2829e04e886f57a389769e2d4a54d17f9df13b0" + } + ], + "blockNumber": 95554959, + "cumulativeGasUsed": "5988090", + "status": 1, + "byzantium": true + }, + "args": [ + "0x6592b5DE802159F3E74B2486b091D11a8256ab8A", + "0xA07c5b74C9B40447a954e1466938b865b6BBea36", + "0x4BD17003473389A42DAF6a0a729f6Fdb328BbBd7" + ], + "numDeployments": 1, + "solcInputHash": "b08018e2eab5f4ca5e3ba0c1798444d8", + "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract ResilientOracleInterface\",\"name\":\"_resilientOracle\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nativeMarketAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"vaiAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"lastProtectionTriggeredAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"}],\"name\":\"CooldownNotElapsed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidArrayLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"}],\"name\":\"InvalidKeeperAction\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"newMax\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"currentSpot\",\"type\":\"uint256\"}],\"name\":\"InvalidMaxPrice\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"newMin\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"currentSpot\",\"type\":\"uint256\"}],\"name\":\"InvalidMinPrice\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"resetThreshold\",\"type\":\"uint256\"}],\"name\":\"InvalidResetThreshold\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"MarketAlreadyInitialized\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"MarketNotInitialized\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"}],\"name\":\"PriceExceedsUint128\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"currentRangeRatio\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"resetThreshold\",\"type\":\"uint256\"}],\"name\":\"PriceRangeNotConverged\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"ProtectedPriceActive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"ProtectedPriceInactive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maximum\",\"type\":\"uint256\"}],\"name\":\"ThresholdAboveMaximum\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minimum\",\"type\":\"uint256\"}],\"name\":\"ThresholdBelowMinimum\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"calledContract\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"methodSignature\",\"type\":\"string\"}],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"VAINotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroPriceNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroValueNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"whitelisted\",\"type\":\"bool\"}],\"name\":\"BoundedPricingWhitelistUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"oldEnabled\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"newEnabled\",\"type\":\"bool\"}],\"name\":\"CachingEnabledUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"oldCooldown\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"newCooldown\",\"type\":\"uint64\"}],\"name\":\"CooldownPeriodSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"oldMax\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"newMax\",\"type\":\"uint128\"}],\"name\":\"MaxPriceUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"oldMin\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"newMin\",\"type\":\"uint128\"}],\"name\":\"MinPriceUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldAccessControlManager\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAccessControlManager\",\"type\":\"address\"}],\"name\":\"NewAccessControlManager\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"minPrice\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"maxPrice\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"triggerThreshold\",\"type\":\"uint256\"}],\"name\":\"ProtectionInitialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"ProtectionModeExited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"spotPrice\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"minPrice\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"maxPrice\",\"type\":\"uint128\"}],\"name\":\"ProtectionTriggered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldExitThreshold\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newExitThreshold\",\"type\":\"uint256\"}],\"name\":\"ResetThresholdSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldThreshold\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newThreshold\",\"type\":\"uint256\"}],\"name\":\"TriggerThresholdSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"COLLATERAL_PRICE_CACHE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DEBT_PRICE_CACHE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"KEEPER_DEADBAND\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_THRESHOLD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MIN_THRESHOLD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"NATIVE_TOKEN_ADDR\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RESILIENT_ORACLE\",\"outputs\":[{\"internalType\":\"contract ResilientOracleInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"accessControlManager\",\"outputs\":[{\"internalType\":\"contract IAccessControlManagerV8\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"allAssets\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"assetProtectionConfig\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"minPrice\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"maxPrice\",\"type\":\"uint128\"},{\"internalType\":\"bool\",\"name\":\"currentlyUsingProtectedPrice\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"isBoundedPricingEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"lastProtectionTriggeredAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"triggerThreshold\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"resetThreshold\",\"type\":\"uint128\"},{\"internalType\":\"bool\",\"name\":\"cachingEnabled\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"canExitProtection\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"assets\",\"type\":\"address[]\"},{\"internalType\":\"uint128[]\",\"name\":\"proposedMins\",\"type\":\"uint128[]\"},{\"internalType\":\"uint128[]\",\"name\":\"proposedMaxs\",\"type\":\"uint128[]\"}],\"name\":\"checkAndGetWindowDrift\",\"outputs\":[{\"internalType\":\"bool[]\",\"name\":\"needsMinUpdate\",\"type\":\"bool[]\"},{\"internalType\":\"bool[]\",\"name\":\"needsMaxUpdate\",\"type\":\"bool[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"currentlyUsingProtectedPrice\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"exitProtectionMode\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllBoundedPricingEnabledAssets\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedCollateralPrice\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"collateralPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedCollateralPriceView\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"collateralPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedDebtPrice\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debtPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedDebtPriceView\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debtPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedPrices\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"collateralPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"debtPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedPricesView\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"collateralPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"debtPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getInitializedAssets\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"accessControlManager_\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"isBoundedPricingEnabled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nativeMarket\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"accessControlManager_\",\"type\":\"address\"}],\"name\":\"setAccessControlManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"}],\"name\":\"setAssetBoundedPricingEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"}],\"name\":\"setCachingEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"newCooldown\",\"type\":\"uint64\"}],\"name\":\"setCooldownPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newTriggerThreshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"newResetThreshold\",\"type\":\"uint256\"}],\"name\":\"setThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"triggerThreshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"resetThreshold\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"enableBoundedPricing\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"enableCaching\",\"type\":\"bool\"}],\"internalType\":\"struct IDeviationBoundedOracle.TokenConfigInput\",\"name\":\"tokenConfig_\",\"type\":\"tuple\"}],\"name\":\"setTokenConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"triggerThreshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"resetThreshold\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"enableBoundedPricing\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"enableCaching\",\"type\":\"bool\"}],\"internalType\":\"struct IDeviationBoundedOracle.TokenConfigInput[]\",\"name\":\"tokenConfigs_\",\"type\":\"tuple[]\"}],\"name\":\"setTokenConfigs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"enum IDeviationBoundedOracle.KeeperAction\",\"name\":\"action\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"internalType\":\"struct IDeviationBoundedOracle.KeeperActionItem[]\",\"name\":\"actions\",\"type\":\"tuple[]\"}],\"name\":\"syncPriceBoundsAndProtections\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"newMax\",\"type\":\"uint128\"}],\"name\":\"updateMaxPrice\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"newMin\",\"type\":\"uint128\"}],\"name\":\"updateMinPrice\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"updateProtectionState\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vai\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Venus\",\"events\":{\"Initialized(uint8)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"}},\"kind\":\"dev\",\"methods\":{\"acceptOwnership()\":{\"details\":\"The new owner accepts the ownership transfer.\"},\"canExitProtection(address)\":{\"details\":\"Returns true when both conditions are met: 1. Cooldown period has elapsed since last trigger 2. Price range has converged below exit threshold\",\"params\":{\"asset\":\"The underlying asset address\"},\"returns\":{\"_0\":\"True if protection can be disabled\"}},\"checkAndGetWindowDrift(address[],uint128[],uint128[])\":{\"custom:error\":\"InvalidArrayLength if the input array lengths do not match\",\"details\":\"Allows the keeper to identify stale windows in a single call, avoiding N individual reads. Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)\",\"params\":{\"assets\":\"Array of asset addresses to check\",\"proposedMaxs\":\"Keeper's off-chain window maximum prices\",\"proposedMins\":\"Keeper's off-chain window minimum prices\"},\"returns\":{\"needsMaxUpdate\":\"Whether maxPrice drift exceeds deadband for each asset\",\"needsMinUpdate\":\"Whether minPrice drift exceeds deadband for each asset\"}},\"constructor\":{\"custom:oz-upgrades-unsafe-allow\":\"constructor\",\"params\":{\"_resilientOracle\":\"Address of the ResilientOracle contract\",\"nativeMarketAddress\":\"The address of a native market (for bsc it would be vBNB address)\",\"vaiAddress\":\"The address of the VAI token, or address(0) if VAI is not deployed on the chain.\"}},\"currentlyUsingProtectedPrice(address)\":{\"params\":{\"asset\":\"The underlying asset address\"},\"returns\":{\"_0\":\"True if the asset is currently using the protected price instead of spot\"}},\"exitProtectionMode(address)\":{\"custom:access\":\"Only authorized monitor/keeper addresses\",\"custom:error\":\"ProtectedPriceInactive if protection is not currently activeCooldownNotElapsed if cooldown period has not elapsedPriceRangeNotConverged if window range is still above exit threshold\",\"custom:event\":\"ProtectionModeExited\",\"details\":\"Called by the keeper/monitor after confirming price has normalised. Enforces two conditions on-chain: 1. Cooldown period has elapsed since the last trigger 2. Price range has converged below the exit threshold\",\"params\":{\"asset\":\"The underlying asset address\"}},\"getAllBoundedPricingEnabledAssets()\":{\"details\":\"Iterates the append-only allAssets array and filters by isBoundedPricingEnabled. Gas-free for off-chain callers.\",\"returns\":{\"_0\":\"result Array of whitelisted asset addresses\"}},\"getBoundedCollateralPrice(address)\":{\"custom:event\":\"MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold\",\"details\":\"Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns the conservative (lower) price when protection is active. Used by keepers or direct callers who want atomic update + read.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"collateralPrice\":\"The bounded collateral price\"}},\"getBoundedCollateralPriceView(address)\":{\"details\":\"Reads from transient cache first when the asset's `cachingEnabled` flag is `true` (populated by a prior updateProtectionState call in the same transaction). Falls back to ResilientOracle on cache miss or when caching is disabled. Returns min(spot, windowMin) when protection is active, spot otherwise.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"collateralPrice\":\"The bounded collateral price\"}},\"getBoundedDebtPrice(address)\":{\"custom:event\":\"MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold\",\"details\":\"Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns the conservative (higher) price when protection is active. Used by keepers or direct callers who want atomic update + read.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"debtPrice\":\"The bounded debt price\"}},\"getBoundedDebtPriceView(address)\":{\"details\":\"Reads from transient cache first when the asset's `cachingEnabled` flag is `true` (populated by a prior updateProtectionState call in the same transaction). Falls back to ResilientOracle on cache miss or when caching is disabled. Returns max(spot, windowMax) when protection is active, spot otherwise.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"debtPrice\":\"The bounded debt price\"}},\"getBoundedPrices(address)\":{\"custom:event\":\"MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold\",\"details\":\"Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns both conservative prices in a single call.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"collateralPrice\":\"The bounded collateral price\",\"debtPrice\":\"The bounded debt price\"}},\"getBoundedPricesView(address)\":{\"details\":\"Reads from transient cache first when the asset's `cachingEnabled` flag is `true`; falls back to ResilientOracle on cache miss or when caching is disabled.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"collateralPrice\":\"The bounded collateral price\",\"debtPrice\":\"The bounded debt price\"}},\"getInitializedAssets()\":{\"returns\":{\"_0\":\"Array of all initialized asset addresses\"}},\"initialize(address)\":{\"params\":{\"accessControlManager_\":\"Address of the access control manager contract\"}},\"isBoundedPricingEnabled(address)\":{\"params\":{\"asset\":\"The underlying asset address\"},\"returns\":{\"_0\":\"True if the asset is whitelisted\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"pendingOwner()\":{\"details\":\"Returns the address of the pending owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"setAccessControlManager(address)\":{\"custom:access\":\"Only Governance\",\"custom:event\":\"Emits NewAccessControlManager event\",\"details\":\"Admin function to set address of AccessControlManager\",\"params\":{\"accessControlManager_\":\"The new address of the AccessControlManager\"}},\"setAssetBoundedPricingEnabled(address,bool)\":{\"custom:access\":\"Only Governance\",\"custom:error\":\"ProtectedPriceActive if trying to disable an asset while protection is active\",\"custom:event\":\"BoundedPricingWhitelistUpdated\",\"params\":{\"asset\":\"The underlying asset address\",\"enabled\":\"Whether bounded pricing should be enabled for the asset\"}},\"setCachingEnabled(address,bool)\":{\"custom:access\":\"Only Governance\",\"custom:error\":\"MarketNotInitialized if the asset has not been initialized\",\"custom:event\":\"CachingEnabledUpdated\",\"details\":\"When disabled, each view/non-view price call recomputes bounded prices from the live spot instead of reading or writing the transient slots. The initial value is set via the `enableCaching` argument of `setTokenConfig`.\",\"params\":{\"asset\":\"The underlying asset address\",\"enabled\":\"Whether transient caching is enabled for this asset\"}},\"setCooldownPeriod(address,uint64)\":{\"custom:access\":\"Only Governance\",\"custom:event\":\"CooldownPeriodSet\",\"params\":{\"asset\":\"The underlying asset address\",\"newCooldown\":\"The new cooldown period in seconds\"}},\"setThresholds(address,uint256,uint256)\":{\"custom:access\":\"Only Governance\",\"custom:error\":\"ThresholdBelowMinimum if newTriggerThreshold is below 5%ThresholdAboveMaximum if newTriggerThreshold is above 50%InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\",\"custom:event\":\"TriggerThresholdSet if the trigger threshold changedResetThresholdSet if the reset threshold changed\",\"params\":{\"asset\":\"The underlying asset address\",\"newResetThreshold\":\"The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\",\"newTriggerThreshold\":\"The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\"}},\"setTokenConfig((address,uint64,uint256,uint256,bool,bool))\":{\"custom:access\":\"Only Governance\",\"custom:event\":\"ProtectionInitializedBoundedPricingWhitelistUpdated\",\"params\":{\"tokenConfig_\":\"Token config input for the asset\"}},\"setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])\":{\"custom:access\":\"Only Governance\",\"custom:error\":\"InvalidArrayLength if the input array is empty\",\"custom:event\":\"ProtectionInitialized for each assetBoundedPricingWhitelistUpdated for each asset\",\"params\":{\"tokenConfigs_\":\"Array of token config inputs, one per asset\"}},\"syncPriceBoundsAndProtections((address,uint8,uint256)[])\":{\"custom:access\":\"Only authorized keeper addresses\",\"custom:error\":\"InvalidKeeperAction if an item carries an unsupported action enum value\",\"custom:event\":\"MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\",\"details\":\"Each item is processed in array order; any item revert rolls back the whole batch. `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode. Empty `actions` is a no-op success.\",\"params\":{\"actions\":\"The list of keeper actions to apply\"}},\"transferOwnership(address)\":{\"details\":\"Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner.\"},\"updateMaxPrice(address,uint128)\":{\"custom:access\":\"Only authorized keeper addresses\",\"custom:event\":\"MaxPriceUpdated\",\"details\":\"Called by the keeper to push corrected max values from the off-chain sliding window. Constraint: newMax must be at or above the current spot price.\",\"params\":{\"asset\":\"The underlying asset address\",\"newMax\":\"The new maximum price\"}},\"updateMinPrice(address,uint128)\":{\"custom:access\":\"Only authorized keeper addresses\",\"custom:event\":\"MinPriceUpdated\",\"details\":\"Called by the keeper to push corrected min values from the off-chain sliding window. Constraint: newMin must be at or below the current spot price.\",\"params\":{\"asset\":\"The underlying asset address\",\"newMin\":\"The new minimum price\"}},\"updateProtectionState(address)\":{\"custom:event\":\"MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold\",\"details\":\"Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before liquidity calculations). Subsequent calls to getBoundedCollateralPriceView / getBoundedDebtPriceView within the same transaction will read from the transient cache instead of querying ResilientOracle again, keeping those functions as `view` and avoiding redundant oracle calls. The transient cache is only populated when the asset's `cachingEnabled` flag is `true`. When caching is disabled, view price reads fall through to live recomputation. Permissionless: anyone can call this, both for gas optimisation and to ensure every caller in the same transaction reads the correct, up-to-date bounded price.\",\"params\":{\"vToken\":\"vToken address\"}}},\"stateVariables\":{\"COLLATERAL_PRICE_CACHE_SLOT\":{\"details\":\"custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache keccak256(abi.encode(uint256(keccak256(\\\"venus-protocol/oracle/DeviationBoundedOracle/collateralCache\\\")) - 1)) & ~bytes32(uint256(0xff))\"},\"DEBT_PRICE_CACHE_SLOT\":{\"details\":\"custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache keccak256(abi.encode(uint256(keccak256(\\\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\\\")) - 1)) & ~bytes32(uint256(0xff))\"},\"RESILIENT_ORACLE\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"nativeMarket\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"vai\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"}},\"title\":\"DeviationBoundedOracle\",\"version\":1},\"userdoc\":{\"errors\":{\"CooldownNotElapsed(address,uint64,uint64)\":[{\"notice\":\"Thrown when trying to disable protection before cooldown has elapsed\"}],\"InvalidArrayLength()\":[{\"notice\":\"Thrown when the lengths of the arrays are not equal\"}],\"InvalidKeeperAction(uint8)\":[{\"notice\":\"Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\"}],\"InvalidMaxPrice(address,uint128,uint256)\":[{\"notice\":\"Thrown when keeper tries to set maxPrice below current spot\"}],\"InvalidMinPrice(address,uint128,uint256)\":[{\"notice\":\"Thrown when keeper tries to set minPrice above current spot\"}],\"InvalidResetThreshold(uint256)\":[{\"notice\":\"Thrown when the exit threshold is set at or above the trigger threshold\"}],\"MarketAlreadyInitialized(address)\":[{\"notice\":\"Thrown when trying to initialize an already initialized market\"}],\"MarketNotInitialized(address)\":[{\"notice\":\"Thrown when trying to use or update protection for an asset that has not been initialized\"}],\"PriceExceedsUint128(uint256)\":[{\"notice\":\"Thrown when a price exceeds uint128 max\"}],\"PriceRangeNotConverged(address,uint256,uint256)\":[{\"notice\":\"Thrown when trying to disable protection before price range has converged\"}],\"ProtectedPriceActive(address)\":[{\"notice\":\"Thrown when trying to disable bounded pricing for an asset while protection is active\"}],\"ProtectedPriceInactive(address)\":[{\"notice\":\"Thrown when trying to disable protection that is not active\"}],\"ThresholdAboveMaximum(uint256,uint256)\":[{\"notice\":\"Thrown when threshold is set above the maximum allowed value\"}],\"ThresholdBelowMinimum(uint256,uint256)\":[{\"notice\":\"Thrown when threshold is set below the minimum allowed value\"}],\"Unauthorized(address,address,string)\":[{\"notice\":\"Thrown when the action is prohibited by AccessControlManager\"}],\"VAINotAllowed()\":[{\"notice\":\"Thrown when trying to initialize protection for VAI\"}],\"ZeroAddressNotAllowed()\":[{\"notice\":\"Thrown if the supplied address is a zero address where it is not allowed\"}],\"ZeroPriceNotAllowed()\":[{\"notice\":\"Thrown when a zero price is provided where a non-zero price is required\"}],\"ZeroValueNotAllowed()\":[{\"notice\":\"Thrown if the supplied value is 0 where it is not allowed\"}]},\"events\":{\"BoundedPricingWhitelistUpdated(address,bool)\":{\"notice\":\"Emitted when an asset's whitelist status changes\"},\"CachingEnabledUpdated(address,bool,bool)\":{\"notice\":\"Emitted when the per-asset transient caching flag is toggled\"},\"CooldownPeriodSet(address,uint64,uint64)\":{\"notice\":\"Emitted when the cooldown period is updated for an asset\"},\"MaxPriceUpdated(address,uint128,uint128)\":{\"notice\":\"Emitted when the keeper updates the maximum price for an asset\"},\"MinPriceUpdated(address,uint128,uint128)\":{\"notice\":\"Emitted when the keeper updates the minimum price for an asset\"},\"NewAccessControlManager(address,address)\":{\"notice\":\"Emitted when access control manager contract address is changed\"},\"ProtectionInitialized(address,uint128,uint128,uint64,uint256)\":{\"notice\":\"Emitted when protection is initialized for an asset\"},\"ProtectionModeExited(address)\":{\"notice\":\"Emitted when protection mode is disabled for an asset\"},\"ProtectionTriggered(address,uint256,uint128,uint128)\":{\"notice\":\"Emitted when protection mode is triggered for an asset\"},\"ResetThresholdSet(address,uint256,uint256)\":{\"notice\":\"Emitted when the exit threshold is updated for an asset\"},\"TriggerThresholdSet(address,uint256,uint256)\":{\"notice\":\"Emitted when the entry threshold is updated for an asset\"}},\"kind\":\"user\",\"methods\":{\"COLLATERAL_PRICE_CACHE_SLOT()\":{\"notice\":\"Transient storage slot for caching final collateral prices within a transaction\"},\"DEBT_PRICE_CACHE_SLOT()\":{\"notice\":\"Transient storage slot for caching final debt prices within a transaction\"},\"KEEPER_DEADBAND()\":{\"notice\":\"Keeper deadband threshold (5%) \\u2014 min/max corrections below this are suppressed\"},\"MAX_THRESHOLD()\":{\"notice\":\"Maximum allowed threshold value (50%)\"},\"MIN_THRESHOLD()\":{\"notice\":\"Minimum allowed threshold value (5%) to account for keeper deadband\"},\"NATIVE_TOKEN_ADDR()\":{\"notice\":\"Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc) and can serve as any underlying asset of a market that supports native tokens\"},\"RESILIENT_ORACLE()\":{\"notice\":\"Resilient Oracle used to fetch spot prices\"},\"accessControlManager()\":{\"notice\":\"Returns the address of the access control manager contract\"},\"allAssets(uint256)\":{\"notice\":\"Append-only array of all assets ever initialized, used for enumeration\"},\"assetProtectionConfig(address)\":{\"notice\":\"Per-asset protection state\"},\"canExitProtection(address)\":{\"notice\":\"Checks if protection can be exited for an asset\"},\"checkAndGetWindowDrift(address[],uint128[],uint128[])\":{\"notice\":\"Batch-checks which assets' on-chain min/max have drifted beyond the deadband from the keeper's proposed window values\"},\"constructor\":{\"notice\":\"Constructor for the implementation contract. Sets immutable variables.\"},\"currentlyUsingProtectedPrice(address)\":{\"notice\":\"Checks if the asset is currently using the protected (bounded) price\"},\"exitProtectionMode(address)\":{\"notice\":\"Exits protection mode for a given asset\"},\"getAllBoundedPricingEnabledAssets()\":{\"notice\":\"Returns all currently whitelisted asset addresses\"},\"getBoundedCollateralPrice(address)\":{\"notice\":\"Gets the bounded collateral price for a given vToken, updating protection state\"},\"getBoundedCollateralPriceView(address)\":{\"notice\":\"Gets the bounded collateral price for a given vToken (view variant)\"},\"getBoundedDebtPrice(address)\":{\"notice\":\"Gets the bounded debt price for a given vToken, updating protection state\"},\"getBoundedDebtPriceView(address)\":{\"notice\":\"Gets the bounded debt price for a given vToken (view variant)\"},\"getBoundedPrices(address)\":{\"notice\":\"Gets both the bounded collateral and debt prices for a given vToken, updating protection state\"},\"getBoundedPricesView(address)\":{\"notice\":\"Gets both the bounded collateral and debt prices for a given vToken (view variant)\"},\"getInitializedAssets()\":{\"notice\":\"Returns all asset addresses that have ever been initialized\"},\"initialize(address)\":{\"notice\":\"Initializes the contract admin\"},\"isBoundedPricingEnabled(address)\":{\"notice\":\"Checks if an asset is whitelisted for bounded pricing\"},\"nativeMarket()\":{\"notice\":\"Native market address\"},\"setAccessControlManager(address)\":{\"notice\":\"Sets the address of AccessControlManager\"},\"setAssetBoundedPricingEnabled(address,bool)\":{\"notice\":\"Sets whether an asset is enabled for bounded pricing\"},\"setCachingEnabled(address,bool)\":{\"notice\":\"Toggles transient caching of the bounded (collateral, debt) pair for an asset\"},\"setCooldownPeriod(address,uint64)\":{\"notice\":\"Sets the cooldown period for an asset\"},\"setThresholds(address,uint256,uint256)\":{\"notice\":\"Sets the trigger and reset thresholds for an asset\"},\"setTokenConfig((address,uint64,uint256,uint256,bool,bool))\":{\"notice\":\"Initializes protection for a new asset\"},\"setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])\":{\"notice\":\"Batch-initializes protection for multiple assets in a single transaction\"},\"syncPriceBoundsAndProtections((address,uint8,uint256)[])\":{\"notice\":\"Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\"},\"updateMaxPrice(address,uint128)\":{\"notice\":\"Updates the maximum price in the rolling window for a given asset\"},\"updateMinPrice(address,uint128)\":{\"notice\":\"Updates the minimum price in the rolling window for a given asset\"},\"updateProtectionState(address)\":{\"notice\":\"Fetches the spot price, updates the protection window, and caches the resolved collateral and debt prices in transient storage for the duration of the transaction.\"},\"vai()\":{\"notice\":\"VAI address\"}},\"notice\":\"The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations. It maintains a per-market rolling min/max price window. When the current spot price deviates significantly from the window bounds, protection mode activates automatically and conservative pricing kicks in: - Collateral is valued at min(spot, windowMin) \\u2014 caps collateral value at recent window low - Debt is valued at max(spot, windowMax) \\u2014 floors debt value at recent window high This protects against instantaneous or short-duration price manipulation attacks on low-liquidity collateral tokens. Sustained attacks beyond the window period are expected to be handled by off-chain monitoring systems. The oracle exposes both view and non-view price functions. The non-view variants update the price window and trigger protection. The view variants read stored state only. A transient price cache avoids redundant ResilientOracle calls within the same transaction when updateProtectionState is called before the view price reads.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/DeviationBoundedOracle.sol\":\"DeviationBoundedOracle\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./OwnableUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership} and {acceptOwnership}.\\n *\\n * This module is used through inheritance. It will make available all functions\\n * from parent (Ownable).\\n */\\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\\n address private _pendingOwner;\\n\\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\\n\\n function __Ownable2Step_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable2Step_init_unchained() internal onlyInitializing {\\n }\\n /**\\n * @dev Returns the address of the pending owner.\\n */\\n function pendingOwner() public view virtual returns (address) {\\n return _pendingOwner;\\n }\\n\\n /**\\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual override onlyOwner {\\n _pendingOwner = newOwner;\\n emit OwnershipTransferStarted(owner(), newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual override {\\n delete _pendingOwner;\\n super._transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev The new owner accepts the ownership transfer.\\n */\\n function acceptOwnership() public virtual {\\n address sender = _msgSender();\\n require(pendingOwner() == sender, \\\"Ownable2Step: caller is not the new owner\\\");\\n _transferOwnership(sender);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x9140dabc466abab21b48b72dbda26736b1183a310d0e677d3719d201df026510\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/ContextUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n function __Ownable_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable_init_unchained() internal onlyInitializing {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x359a1ab89b46b9aba7bcad3fb651924baf4893d15153049b9976b0fc9be1358e\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\\n *\\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\\n * reused. This mechanism prevents re-execution of each \\\"step\\\" but allows the creation of new initialization steps in\\n * case an upgrade adds a module that needs to be initialized.\\n *\\n * For example:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```solidity\\n * contract MyToken is ERC20Upgradeable {\\n * function initialize() initializer public {\\n * __ERC20_init(\\\"MyToken\\\", \\\"MTK\\\");\\n * }\\n * }\\n *\\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\\n * function initializeV2() reinitializer(2) public {\\n * __ERC20Permit_init(\\\"MyToken\\\");\\n * }\\n * }\\n * ```\\n *\\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\\n *\\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\\n *\\n * [CAUTION]\\n * ====\\n * Avoid leaving a contract uninitialized.\\n *\\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * /// @custom:oz-upgrades-unsafe-allow constructor\\n * constructor() {\\n * _disableInitializers();\\n * }\\n * ```\\n * ====\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n * @custom:oz-retyped-from bool\\n */\\n uint8 private _initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private _initializing;\\n\\n /**\\n * @dev Triggered when the contract has been initialized or reinitialized.\\n */\\n event Initialized(uint8 version);\\n\\n /**\\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\\n * `onlyInitializing` functions can be used to initialize parent contracts.\\n *\\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\\n * constructor.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier initializer() {\\n bool isTopLevelCall = !_initializing;\\n require(\\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n _initialized = 1;\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(1);\\n }\\n }\\n\\n /**\\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\\n * used to initialize parent contracts.\\n *\\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\\n * are added through upgrades and that require initialization.\\n *\\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\\n * cannot be nested. If one is invoked in the context of another, execution will revert.\\n *\\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\\n * a contract, executing them in the right order is up to the developer or operator.\\n *\\n * WARNING: setting the version to 255 will prevent any future reinitialization.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier reinitializer(uint8 version) {\\n require(!_initializing && _initialized < version, \\\"Initializable: contract is already initialized\\\");\\n _initialized = version;\\n _initializing = true;\\n _;\\n _initializing = false;\\n emit Initialized(version);\\n }\\n\\n /**\\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\\n */\\n modifier onlyInitializing() {\\n require(_initializing, \\\"Initializable: contract is not initializing\\\");\\n _;\\n }\\n\\n /**\\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\\n * through proxies.\\n *\\n * Emits an {Initialized} event the first time it is successfully executed.\\n */\\n function _disableInitializers() internal virtual {\\n require(!_initializing, \\\"Initializable: contract is initializing\\\");\\n if (_initialized != type(uint8).max) {\\n _initialized = type(uint8).max;\\n emit Initialized(type(uint8).max);\\n }\\n }\\n\\n /**\\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\\n */\\n function _getInitializedVersion() internal view returns (uint8) {\\n return _initialized;\\n }\\n\\n /**\\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\\n */\\n function _isInitializing() internal view returns (bool) {\\n return _initializing;\\n }\\n}\\n\",\"keccak256\":\"0x89be10e757d242e9b18d5a32c9fbe2019f6d63052bbe46397a430a1d60d7f794\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary AddressUpgradeable {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n *\\n * Furthermore, `isContract` will also return true if the target contract within\\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\\n * which only has an effect at the end of a transaction.\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\\n *\\n * _Available since v4.8._\\n */\\n function verifyCallResultFromTarget(\\n address target,\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n if (success) {\\n if (returndata.length == 0) {\\n // only check isContract if the call was successful and the return data is empty\\n // otherwise we already know that it was a contract\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n }\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason or using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n /// @solidity memory-safe-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9c80f545915582e63fe206c6ce27cbe85a86fc10b9cd2a0e8c9488fb7c2ee422\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract ContextUpgradeable is Initializable {\\n function __Context_init() internal onlyInitializing {\\n }\\n\\n function __Context_init_unchained() internal onlyInitializing {\\n }\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[50] private __gap;\\n}\\n\",\"keccak256\":\"0x75097e35253e7fb282ee4d7f27a80eaacfa759923185bf17302a89cbc059c5ef\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\\n}\\n\",\"keccak256\":\"0x287b55befed2961a7eabd7d7b1b2839cbca8a5b80ef8dcbb25ed3d4c2002c305\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport \\\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\\\";\\nimport \\\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\\\";\\n\\nimport \\\"./IAccessControlManagerV8.sol\\\";\\n\\n/**\\n * @title AccessControlledV8\\n * @author Venus\\n * @notice This contract is helper between access control manager and actual contract. This contract further inherited by other contract (using solidity 0.8.13)\\n * to integrate access controlled mechanism. It provides initialise methods and verifying access methods.\\n */\\nabstract contract AccessControlledV8 is Initializable, Ownable2StepUpgradeable {\\n /// @notice Access control manager contract\\n IAccessControlManagerV8 internal _accessControlManager;\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n\\n /// @notice Emitted when access control manager contract address is changed\\n event NewAccessControlManager(address oldAccessControlManager, address newAccessControlManager);\\n\\n /// @notice Thrown when the action is prohibited by AccessControlManager\\n error Unauthorized(address sender, address calledContract, string methodSignature);\\n\\n function __AccessControlled_init(address accessControlManager_) internal onlyInitializing {\\n __Ownable2Step_init();\\n __AccessControlled_init_unchained(accessControlManager_);\\n }\\n\\n function __AccessControlled_init_unchained(address accessControlManager_) internal onlyInitializing {\\n _setAccessControlManager(accessControlManager_);\\n }\\n\\n /**\\n * @notice Sets the address of AccessControlManager\\n * @dev Admin function to set address of AccessControlManager\\n * @param accessControlManager_ The new address of the AccessControlManager\\n * @custom:event Emits NewAccessControlManager event\\n * @custom:access Only Governance\\n */\\n function setAccessControlManager(address accessControlManager_) external onlyOwner {\\n _setAccessControlManager(accessControlManager_);\\n }\\n\\n /**\\n * @notice Returns the address of the access control manager contract\\n */\\n function accessControlManager() external view returns (IAccessControlManagerV8) {\\n return _accessControlManager;\\n }\\n\\n /**\\n * @dev Internal function to set address of AccessControlManager\\n * @param accessControlManager_ The new address of the AccessControlManager\\n */\\n function _setAccessControlManager(address accessControlManager_) internal {\\n require(address(accessControlManager_) != address(0), \\\"invalid acess control manager address\\\");\\n address oldAccessControlManager = address(_accessControlManager);\\n _accessControlManager = IAccessControlManagerV8(accessControlManager_);\\n emit NewAccessControlManager(oldAccessControlManager, accessControlManager_);\\n }\\n\\n /**\\n * @notice Reverts if the call is not allowed by AccessControlManager\\n * @param signature Method signature\\n */\\n function _checkAccessAllowed(string memory signature) internal view {\\n bool isAllowedToCall = _accessControlManager.isAllowedToCall(msg.sender, signature);\\n\\n if (!isAllowedToCall) {\\n revert Unauthorized(msg.sender, address(this), signature);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfe0fd441c84ac907cabc88db69ef04f6d7532d770c7e6a1dfe6e7d6305debb49\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport \\\"@openzeppelin/contracts/access/IAccessControl.sol\\\";\\n\\n/**\\n * @title IAccessControlManagerV8\\n * @author Venus\\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\\n */\\ninterface IAccessControlManagerV8 is IAccessControl {\\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\\n\\n function revokeCallPermission(\\n address contractAddress,\\n string calldata functionSig,\\n address accountToRevoke\\n ) external;\\n\\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\\n\\n function hasPermission(\\n address account,\\n address contractAddress,\\n string calldata functionSig\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xaa29b098440d0b3a131c5ecdf25ce548790c1b5ac7bf9b5c0264b6af6f7a1e0b\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/constants.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\\nuint256 constant EXP_SCALE = 1e18;\\n\\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\\nuint256 constant MANTISSA_ONE = EXP_SCALE;\\n\\n/// @dev The approximate number of seconds per year\\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\\n\",\"keccak256\":\"0x14de93ead464da249af31bea0e3bcfb62ec693bea3475fb4d90f055ac81dc5eb\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/validators.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\\nerror ZeroAddressNotAllowed();\\n\\n/// @notice Thrown if the supplied value is 0 where it is not allowed\\nerror ZeroValueNotAllowed();\\n\\n/// @notice Checks if the provided address is nonzero, reverts otherwise\\n/// @param address_ Address to check\\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\\nfunction ensureNonzeroAddress(address address_) pure {\\n if (address_ == address(0)) {\\n revert ZeroAddressNotAllowed();\\n }\\n}\\n\\n/// @notice Checks if the provided value is nonzero, reverts otherwise\\n/// @param value_ Value to check\\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\\nfunction ensureNonzeroValue(uint256 value_) pure {\\n if (value_ == 0) {\\n revert ZeroValueNotAllowed();\\n }\\n}\\n\",\"keccak256\":\"0xdb88e14d50dd21889ca3329d755673d022c47e8da005b6a545c7f69c2c4b7b86\",\"license\":\"BSD-3-Clause\"},\"contracts/DeviationBoundedOracle.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { VBep20Interface } from \\\"./interfaces/VBep20Interface.sol\\\";\\nimport { ResilientOracleInterface } from \\\"./interfaces/OracleInterface.sol\\\";\\nimport { IDeviationBoundedOracle } from \\\"./interfaces/IDeviationBoundedOracle.sol\\\";\\nimport { AccessControlledV8 } from \\\"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\\\";\\nimport { EXP_SCALE } from \\\"@venusprotocol/solidity-utilities/contracts/constants.sol\\\";\\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \\\"@venusprotocol/solidity-utilities/contracts/validators.sol\\\";\\nimport { Transient } from \\\"./lib/Transient.sol\\\";\\n\\n/**\\n * @title DeviationBoundedOracle\\n * @author Venus\\n * @notice The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations.\\n *\\n * It maintains a per-market rolling min/max price window. When the current spot price deviates\\n * significantly from the window bounds, protection mode activates automatically and conservative\\n * pricing kicks in:\\n * - Collateral is valued at min(spot, windowMin) \\u2014 caps collateral value at recent window low\\n * - Debt is valued at max(spot, windowMax) \\u2014 floors debt value at recent window high\\n *\\n * This protects against instantaneous or short-duration price manipulation attacks on low-liquidity\\n * collateral tokens. Sustained attacks beyond the window period are expected to be handled by\\n * off-chain monitoring systems.\\n *\\n * The oracle exposes both view and non-view price functions. The non-view variants update the\\n * price window and trigger protection. The view variants read stored state only. A transient\\n * price cache avoids redundant ResilientOracle calls within the same transaction when\\n * updateProtectionState is called before the view price reads.\\n */\\ncontract DeviationBoundedOracle is AccessControlledV8, IDeviationBoundedOracle {\\n /// @notice Minimum allowed threshold value (5%) to account for keeper deadband\\n uint256 public constant MIN_THRESHOLD = 5e16;\\n\\n /// @notice Maximum allowed threshold value (50%)\\n uint256 public constant MAX_THRESHOLD = 50e16;\\n\\n /// @notice Keeper deadband threshold (5%) \\u2014 min/max corrections below this are suppressed\\n uint256 public constant KEEPER_DEADBAND = 5e16;\\n\\n /// @notice Resilient Oracle used to fetch spot prices\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\\n\\n /// @notice Native market address\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n address public immutable nativeMarket;\\n\\n /// @notice VAI address\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n address public immutable vai;\\n\\n /// @notice Transient storage slot for caching final collateral prices within a transaction\\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache\\n /// keccak256(abi.encode(uint256(keccak256(\\\"venus-protocol/oracle/DeviationBoundedOracle/collateralCache\\\")) - 1))\\n /// & ~bytes32(uint256(0xff))\\n bytes32 public constant COLLATERAL_PRICE_CACHE_SLOT =\\n 0x7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf7000;\\n\\n /// @notice Transient storage slot for caching final debt prices within a transaction\\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache\\n /// keccak256(abi.encode(uint256(keccak256(\\\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\\\")) - 1))\\n /// & ~bytes32(uint256(0xff))\\n bytes32 public constant DEBT_PRICE_CACHE_SLOT = 0x84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb8970600;\\n\\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\\n /// and can serve as any underlying asset of a market that supports native tokens\\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\\n\\n /// @notice Per-asset protection state\\n mapping(address => MarketProtectionState) public assetProtectionConfig;\\n\\n /// @notice Append-only array of all assets ever initialized, used for enumeration\\n address[] public allAssets;\\n\\n /// @notice Storage gap for upgrades\\n uint256[48] private __gap;\\n\\n /**\\n * @notice Constructor for the implementation contract. Sets immutable variables.\\n * @param _resilientOracle Address of the ResilientOracle contract\\n * @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\\n * @param vaiAddress The address of the VAI token, or address(0) if VAI is not deployed on the chain.\\n * @custom:oz-upgrades-unsafe-allow constructor\\n */\\n constructor(ResilientOracleInterface _resilientOracle, address nativeMarketAddress, address vaiAddress) {\\n ensureNonzeroAddress(address(_resilientOracle));\\n ensureNonzeroAddress(nativeMarketAddress);\\n RESILIENT_ORACLE = _resilientOracle;\\n nativeMarket = nativeMarketAddress;\\n vai = vaiAddress;\\n _disableInitializers();\\n }\\n\\n /**\\n * @notice Initializes the contract admin\\n * @param accessControlManager_ Address of the access control manager contract\\n */\\n function initialize(address accessControlManager_) external initializer {\\n __AccessControlled_init(accessControlManager_);\\n }\\n\\n // ----- Non-view price functions (update window + trigger protection) -----\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\\n * and returns the conservative (lower) price when protection is active.\\n * Used by keepers or direct callers who want atomic update + read.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice) {\\n (collateralPrice, ) = _updateAndGetBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken, updating protection state\\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\\n * and returns the conservative (higher) price when protection is active.\\n * Used by keepers or direct callers who want atomic update + read.\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice) {\\n (, debtPrice) = _updateAndGetBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\\n * and returns both conservative prices in a single call.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice) {\\n return _updateAndGetBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Fetches the spot price, updates the protection window, and caches the resolved\\n * collateral and debt prices in transient storage for the duration of the transaction.\\n * @dev Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before\\n * liquidity calculations). Subsequent calls to getBoundedCollateralPriceView /\\n * getBoundedDebtPriceView within the same transaction will read from the transient cache\\n * instead of querying ResilientOracle again, keeping those functions as `view` and\\n * avoiding redundant oracle calls.\\n * The transient cache is only populated when the asset's `cachingEnabled` flag is `true`.\\n * When caching is disabled, view price reads fall through to live recomputation.\\n * Permissionless: anyone can call this, both for gas optimisation and to ensure every\\n * caller in the same transaction reads the correct, up-to-date bounded price.\\n * @param vToken vToken address\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function updateProtectionState(address vToken) external {\\n _updateAndGetBoundedPrices(vToken);\\n }\\n\\n // ----- View price functions (read stored/cached state only) -----\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`\\n * (populated by a prior updateProtectionState call in the same transaction). Falls back\\n * to ResilientOracle on cache miss or when caching is disabled.\\n * Returns min(spot, windowMin) when protection is active, spot otherwise.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n */\\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 collateralPrice) {\\n (collateralPrice, ) = _computeBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`\\n * (populated by a prior updateProtectionState call in the same transaction). Falls back\\n * to ResilientOracle on cache miss or when caching is disabled.\\n * Returns max(spot, windowMax) when protection is active, spot otherwise.\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n */\\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 debtPrice) {\\n (, debtPrice) = _computeBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n */\\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice) {\\n return _computeBoundedPrices(vToken);\\n }\\n\\n // ----- Keeper functions -----\\n\\n /**\\n * @notice Updates the minimum price in the rolling window for a given asset\\n * @dev Called by the keeper to push corrected min values from the off-chain sliding window.\\n * Constraint: newMin must be at or below the current spot price.\\n * @param asset The underlying asset address\\n * @param newMin The new minimum price\\n * @custom:access Only authorized keeper addresses\\n * @custom:event MinPriceUpdated\\n */\\n function updateMinPrice(address asset, uint128 newMin) external {\\n _checkAccessAllowed(\\\"updateMinPrice(address,uint128)\\\");\\n _validateAndUpdateBound(asset, newMin, PriceBoundType.MIN);\\n }\\n\\n /**\\n * @notice Updates the maximum price in the rolling window for a given asset\\n * @dev Called by the keeper to push corrected max values from the off-chain sliding window.\\n * Constraint: newMax must be at or above the current spot price.\\n * @param asset The underlying asset address\\n * @param newMax The new maximum price\\n * @custom:access Only authorized keeper addresses\\n * @custom:event MaxPriceUpdated\\n */\\n function updateMaxPrice(address asset, uint128 newMax) external {\\n _checkAccessAllowed(\\\"updateMaxPrice(address,uint128)\\\");\\n _validateAndUpdateBound(asset, newMax, PriceBoundType.MAX);\\n }\\n\\n /**\\n * @notice Exits protection mode for a given asset\\n * @dev Called by the keeper/monitor after confirming price has normalised.\\n * Enforces two conditions on-chain:\\n * 1. Cooldown period has elapsed since the last trigger\\n * 2. Price range has converged below the exit threshold\\n * @param asset The underlying asset address\\n * @custom:access Only authorized monitor/keeper addresses\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\\n * @custom:error PriceRangeNotConverged if window range is still above exit threshold\\n * @custom:event ProtectionModeExited\\n */\\n function exitProtectionMode(address asset) external {\\n _checkAccessAllowed(\\\"exitProtectionMode(address)\\\");\\n _exitProtectionMode(asset);\\n }\\n\\n /**\\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\\n * Empty `actions` is a no-op success.\\n * @param actions The list of keeper actions to apply\\n * @custom:access Only authorized keeper addresses\\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\\n */\\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external {\\n _checkAccessAllowed(\\\"syncPriceBoundsAndProtections((address,uint8,uint256)[])\\\");\\n uint256 len = actions.length;\\n for (uint256 i; i < len; ++i) {\\n KeeperActionItem calldata item = actions[i];\\n if (item.action == KeeperAction.SetMinPrice) {\\n _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MIN);\\n } else if (item.action == KeeperAction.SetMaxPrice) {\\n _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MAX);\\n } else if (item.action == KeeperAction.ExitProtectionMode) {\\n _exitProtectionMode(item.asset);\\n } else {\\n revert InvalidKeeperAction(uint8(item.action));\\n }\\n }\\n }\\n\\n // ----- Admin functions (governance-gated) -----\\n\\n /**\\n * @notice Initializes protection for a new asset\\n * @param tokenConfig_ Token config input for the asset\\n * @custom:access Only Governance\\n * @custom:event ProtectionInitialized\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external {\\n _checkAccessAllowed(\\\"setTokenConfig((address,uint64,uint256,uint256,bool,bool))\\\");\\n _setTokenConfig(\\n tokenConfig_.asset,\\n tokenConfig_.cooldownPeriod,\\n tokenConfig_.triggerThreshold,\\n tokenConfig_.resetThreshold,\\n tokenConfig_.enableBoundedPricing,\\n tokenConfig_.enableCaching\\n );\\n }\\n\\n /**\\n * @notice Batch-initializes protection for multiple assets in a single transaction\\n * @param tokenConfigs_ Array of token config inputs, one per asset\\n * @custom:access Only Governance\\n * @custom:error InvalidArrayLength if the input array is empty\\n * @custom:event ProtectionInitialized for each asset\\n * @custom:event BoundedPricingWhitelistUpdated for each asset\\n */\\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external {\\n _checkAccessAllowed(\\\"setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])\\\");\\n uint256 len = tokenConfigs_.length;\\n if (len == 0) revert InvalidArrayLength();\\n\\n for (uint256 i; i < len; ++i) {\\n TokenConfigInput calldata tokenConfig = tokenConfigs_[i];\\n _setTokenConfig(\\n tokenConfig.asset,\\n tokenConfig.cooldownPeriod,\\n tokenConfig.triggerThreshold,\\n tokenConfig.resetThreshold,\\n tokenConfig.enableBoundedPricing,\\n tokenConfig.enableCaching\\n );\\n }\\n }\\n\\n /**\\n * @notice Sets the cooldown period for an asset\\n * @param asset The underlying asset address\\n * @param newCooldown The new cooldown period in seconds\\n * @custom:access Only Governance\\n * @custom:event CooldownPeriodSet\\n */\\n function setCooldownPeriod(address asset, uint64 newCooldown) external {\\n _checkAccessAllowed(\\\"setCooldownPeriod(address,uint64)\\\");\\n ensureNonzeroAddress(asset);\\n ensureNonzeroValue(newCooldown);\\n\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n emit CooldownPeriodSet(asset, state.cooldownPeriod, newCooldown);\\n state.cooldownPeriod = newCooldown;\\n }\\n\\n /**\\n * @notice Sets the trigger and reset thresholds for an asset\\n * @param asset The underlying asset address\\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\\n * @custom:access Only Governance\\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\\n * @custom:event TriggerThresholdSet if the trigger threshold changed\\n * @custom:event ResetThresholdSet if the reset threshold changed\\n */\\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external {\\n _checkAccessAllowed(\\\"setThresholds(address,uint256,uint256)\\\");\\n ensureNonzeroAddress(asset);\\n ensureNonzeroValue(newTriggerThreshold);\\n ensureNonzeroValue(newResetThreshold);\\n if (newTriggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(newTriggerThreshold, MIN_THRESHOLD);\\n if (newTriggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(newTriggerThreshold, MAX_THRESHOLD);\\n if (newResetThreshold >= newTriggerThreshold) revert InvalidResetThreshold(newResetThreshold);\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n\\n if (newTriggerThreshold != state.triggerThreshold) {\\n emit TriggerThresholdSet(asset, state.triggerThreshold, newTriggerThreshold);\\n state.triggerThreshold = uint128(newTriggerThreshold);\\n }\\n if (newResetThreshold != state.resetThreshold) {\\n emit ResetThresholdSet(asset, state.resetThreshold, newResetThreshold);\\n state.resetThreshold = uint128(newResetThreshold);\\n }\\n }\\n\\n /**\\n * @notice Sets whether an asset is enabled for bounded pricing\\n * @param asset The underlying asset address\\n * @param enabled Whether bounded pricing should be enabled for the asset\\n * @custom:access Only Governance\\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external {\\n _checkAccessAllowed(\\\"setAssetBoundedPricingEnabled(address,bool)\\\");\\n ensureNonzeroAddress(asset);\\n\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n\\n if (!enabled && state.currentlyUsingProtectedPrice) {\\n revert ProtectedPriceActive(asset);\\n }\\n\\n if (state.isBoundedPricingEnabled == enabled) return;\\n\\n // reset the window if re-enabling\\n if (enabled) {\\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\\n _setMinPrice(state, asset, spotU128);\\n _setMaxPrice(state, asset, spotU128);\\n }\\n\\n state.isBoundedPricingEnabled = enabled;\\n emit BoundedPricingWhitelistUpdated(asset, enabled);\\n }\\n\\n /**\\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\\n * live spot instead of reading or writing the transient slots. The initial value is\\n * set via the `enableCaching` argument of `setTokenConfig`.\\n * @param asset The underlying asset address\\n * @param enabled Whether transient caching is enabled for this asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CachingEnabledUpdated\\n */\\n function setCachingEnabled(address asset, bool enabled) external {\\n _checkAccessAllowed(\\\"setCachingEnabled(address,bool)\\\");\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n emit CachingEnabledUpdated(asset, state.cachingEnabled, enabled);\\n state.cachingEnabled = enabled;\\n }\\n\\n // ----- View helpers -----\\n\\n /**\\n * @notice Returns all asset addresses that have ever been initialized\\n * @return Array of all initialized asset addresses\\n */\\n function getInitializedAssets() external view returns (address[] memory) {\\n return allAssets;\\n }\\n\\n /**\\n * @notice Checks if an asset is whitelisted for bounded pricing\\n * @param asset The underlying asset address\\n * @return True if the asset is whitelisted\\n */\\n function isBoundedPricingEnabled(address asset) external view returns (bool) {\\n return assetProtectionConfig[asset].isBoundedPricingEnabled;\\n }\\n\\n /**\\n * @notice Checks if the asset is currently using the protected (bounded) price\\n * @param asset The underlying asset address\\n * @return True if the asset is currently using the protected price instead of spot\\n */\\n function currentlyUsingProtectedPrice(address asset) external view returns (bool) {\\n return assetProtectionConfig[asset].currentlyUsingProtectedPrice;\\n }\\n\\n /**\\n * @notice Returns all currently whitelisted asset addresses\\n * @dev Iterates the append-only allAssets array and filters by isBoundedPricingEnabled.\\n * Gas-free for off-chain callers.\\n * @return result Array of whitelisted asset addresses\\n */\\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory) {\\n uint256 len = allAssets.length;\\n address[] memory temp = new address[](len);\\n uint256 count;\\n for (uint256 i; i < len; ++i) {\\n if (assetProtectionConfig[allAssets[i]].isBoundedPricingEnabled) {\\n temp[count++] = allAssets[i];\\n }\\n }\\n address[] memory result = new address[](count);\\n for (uint256 i; i < count; ++i) {\\n result[i] = temp[i];\\n }\\n return result;\\n }\\n\\n /**\\n * @notice Checks if protection can be exited for an asset\\n * @dev Returns true when both conditions are met:\\n * 1. Cooldown period has elapsed since last trigger\\n * 2. Price range has converged below exit threshold\\n * @param asset The underlying asset address\\n * @return True if protection can be disabled\\n */\\n function canExitProtection(address asset) external view returns (bool) {\\n MarketProtectionState storage state = assetProtectionConfig[asset];\\n return\\n state.currentlyUsingProtectedPrice &&\\n block.timestamp >= uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod) &&\\n _computePriceBoundRatio(state.minPrice, state.maxPrice) < state.resetThreshold;\\n }\\n\\n /**\\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the deadband\\n * from the keeper's proposed window values\\n * @dev Allows the keeper to identify stale windows in a single call, avoiding N individual reads.\\n * Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)\\n * @param assets Array of asset addresses to check\\n * @param proposedMins Keeper's off-chain window minimum prices\\n * @param proposedMaxs Keeper's off-chain window maximum prices\\n * @return needsMinUpdate Whether minPrice drift exceeds deadband for each asset\\n * @return needsMaxUpdate Whether maxPrice drift exceeds deadband for each asset\\n * @custom:error InvalidArrayLength if the input array lengths do not match\\n */\\n function checkAndGetWindowDrift(\\n address[] calldata assets,\\n uint128[] calldata proposedMins,\\n uint128[] calldata proposedMaxs\\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate) {\\n uint256 len = assets.length;\\n if (len != proposedMins.length || len != proposedMaxs.length) revert InvalidArrayLength();\\n\\n needsMinUpdate = new bool[](len);\\n needsMaxUpdate = new bool[](len);\\n\\n for (uint256 i; i < len; ++i) {\\n MarketProtectionState storage state = assetProtectionConfig[assets[i]];\\n needsMinUpdate[i] = _exceedsCorrectionDeadband(state.minPrice, proposedMins[i]);\\n needsMaxUpdate[i] = _exceedsCorrectionDeadband(state.maxPrice, proposedMaxs[i]);\\n }\\n }\\n\\n // ----- Internal functions -----\\n\\n /**\\n * @notice Initializes protection parameters and price window for a single asset\\n * @dev Fetches the current spot price from ResilientOracle to seed the initial min/max window,\\n * confirming the oracle is live for this asset before it is listed. Both bounds start at\\n * spot so the window expands naturally as prices move. Can only be called once per asset.\\n * @param asset The underlying asset address\\n * @param cooldownPeriod Minimum time protection stays active after last trigger\\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\\n * @param enableBoundedPricing Whether to enable bounded pricing immediately upon initialization\\n * @param enableCaching Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\\n * @custom:error VAINotAllowed if asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n */\\n function _setTokenConfig(\\n address asset,\\n uint64 cooldownPeriod,\\n uint256 triggerThreshold,\\n uint256 resetThreshold,\\n bool enableBoundedPricing,\\n bool enableCaching\\n ) internal {\\n ensureNonzeroAddress(asset);\\n ensureNonzeroValue(cooldownPeriod);\\n ensureNonzeroValue(triggerThreshold);\\n ensureNonzeroValue(resetThreshold);\\n if (assetProtectionConfig[asset].asset != address(0)) revert MarketAlreadyInitialized(asset);\\n if (triggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(triggerThreshold, MIN_THRESHOLD);\\n if (triggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(triggerThreshold, MAX_THRESHOLD);\\n if (resetThreshold >= triggerThreshold) revert InvalidResetThreshold(resetThreshold);\\n if (asset == vai) revert VAINotAllowed();\\n\\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\\n\\n assetProtectionConfig[asset] = MarketProtectionState({\\n minPrice: spotU128,\\n maxPrice: spotU128,\\n currentlyUsingProtectedPrice: false,\\n isBoundedPricingEnabled: enableBoundedPricing,\\n lastProtectionTriggeredAt: 0,\\n cooldownPeriod: cooldownPeriod,\\n asset: asset,\\n triggerThreshold: uint128(triggerThreshold),\\n resetThreshold: uint128(resetThreshold),\\n cachingEnabled: enableCaching\\n });\\n\\n allAssets.push(asset);\\n\\n emit ProtectionInitialized(asset, spotU128, spotU128, cooldownPeriod, triggerThreshold);\\n emit BoundedPricingWhitelistUpdated(asset, enableBoundedPricing);\\n }\\n\\n /**\\n * @notice Validates and applies a keeper-provided min or max price update\\n * @param asset The underlying asset address\\n * @param newPrice The new price value to set\\n * @param boundType Whether this is a MIN or MAX bound update\\n * @custom:error ZeroPriceNotAllowed if newPrice is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMinPrice if boundType is MIN and newPrice exceeds the current spot or is strictly above maxPrice\\n * @custom:error InvalidMaxPrice if boundType is MAX and newPrice is below the current spot or is strictly below minPrice\\n */\\n function _validateAndUpdateBound(address asset, uint128 newPrice, PriceBoundType boundType) internal {\\n ensureNonzeroAddress(asset);\\n if (newPrice == 0) revert ZeroPriceNotAllowed();\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n\\n uint256 currentSpot = _fetchSpotPrice(asset);\\n if (boundType == PriceBoundType.MIN) {\\n if (newPrice > state.maxPrice || uint256(newPrice) > currentSpot)\\n revert InvalidMinPrice(asset, newPrice, currentSpot);\\n _setMinPrice(state, asset, newPrice);\\n } else if (boundType == PriceBoundType.MAX) {\\n if (newPrice < state.minPrice || uint256(newPrice) < currentSpot)\\n revert InvalidMaxPrice(asset, newPrice, currentSpot);\\n _setMaxPrice(state, asset, newPrice);\\n }\\n }\\n\\n /**\\n * @notice Clears protection for an asset once cooldown has elapsed and the window has converged\\n * @dev Shared body of `exitProtectionMode` and the ExitProtectionMode branch of `syncPriceBoundsAndProtections`.\\n * Callers are responsible for ACM gating before invoking this helper.\\n * @param asset The underlying asset address\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\\n */\\n function _exitProtectionMode(address asset) internal {\\n ensureNonzeroAddress(asset);\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n\\n if (!state.currentlyUsingProtectedPrice) revert ProtectedPriceInactive(asset);\\n\\n if (block.timestamp < uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod)) {\\n revert CooldownNotElapsed(asset, state.lastProtectionTriggeredAt, state.cooldownPeriod);\\n }\\n\\n uint256 rangeRatio = _computePriceBoundRatio(state.minPrice, state.maxPrice);\\n if (rangeRatio >= state.resetThreshold) {\\n revert PriceRangeNotConverged(asset, rangeRatio, state.resetThreshold);\\n }\\n\\n state.currentlyUsingProtectedPrice = false;\\n state.lastProtectionTriggeredAt = 0;\\n emit ProtectionModeExited(asset);\\n }\\n\\n /**\\n * @notice Shared non-view logic for all bounded price functions.\\n * Fetches spot, updates window, triggers protection if needed, and returns both bounded prices.\\n * @param vToken vToken address\\n * @return minPrice The bounded lower (collateral) price\\n * @return maxPrice The bounded upper (debt) price\\n */\\n function _updateAndGetBoundedPrices(address vToken) internal returns (uint256 minPrice, uint256 maxPrice) {\\n address asset = _getUnderlyingAsset(vToken);\\n\\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\\n (minPrice, maxPrice) = _getCachedPrices(asset);\\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\\n\\n // return early if failure from resilient oracle to prevent cold SLOAD\\n uint256 spot = _fetchSpotPrice(asset);\\n MarketProtectionState storage state = assetProtectionConfig[asset];\\n if (!state.isBoundedPricingEnabled) {\\n _setCachedPrices(asset, spot, spot);\\n return (spot, spot);\\n }\\n (uint128 updatedMin, uint128 updatedMax, bool windowExpanded) = _expandPriceWindow(state, spot, asset);\\n bool protectionActive = _checkAndTriggerProtection(state, spot, asset, windowExpanded);\\n (minPrice, maxPrice) = _resolveBoundedPrices(protectionActive, spot, uint256(updatedMin), uint256(updatedMax));\\n _setCachedPrices(asset, minPrice, maxPrice);\\n }\\n\\n /**\\n * @dev Expands the price window toward extremes if the spot price is a new min or max\\n * @param state The market protection state\\n * @param spot The current spot price\\n * @param asset The underlying asset address (for event emission)\\n */\\n function _expandPriceWindow(\\n MarketProtectionState storage state,\\n uint256 spot,\\n address asset\\n ) internal returns (uint128, uint128, bool) {\\n uint128 spotU128 = _safeToUint128(spot);\\n uint128 currentMin = state.minPrice;\\n uint128 currentMax = state.maxPrice;\\n bool windowExpanded;\\n if (spotU128 < currentMin) {\\n _setMinPrice(state, asset, spotU128);\\n currentMin = spotU128;\\n windowExpanded = true;\\n }\\n if (spotU128 > currentMax) {\\n _setMaxPrice(state, asset, spotU128);\\n currentMax = spotU128;\\n windowExpanded = true;\\n }\\n return (currentMin, currentMax, windowExpanded);\\n }\\n\\n /**\\n * @dev Checks if the spot price has deviated beyond the threshold and triggers protection.\\n * `lastProtectionTriggeredAt` is reset only on the first trigger or when the price has made a\\n * genuine new extreme this update (windowExpanded == true). Recovery within the existing window\\n * keeps the cooldown ticking so `exitProtectionMode` remains reachable.\\n * @param state The market protection state\\n * @param spot The current spot price\\n * @param asset The underlying asset address (for event emission)\\n * @param windowExpanded True if `_expandPriceWindow` recorded a new low or new high this call\\n */\\n function _checkAndTriggerProtection(\\n MarketProtectionState storage state,\\n uint256 spot,\\n address asset,\\n bool windowExpanded\\n ) internal returns (bool triggered) {\\n if (_exceedsDeviationThreshold(spot, state.minPrice, state.maxPrice, state.triggerThreshold)) {\\n bool enteringProtection = !state.currentlyUsingProtectedPrice;\\n if (enteringProtection || windowExpanded) {\\n state.lastProtectionTriggeredAt = uint64(block.timestamp);\\n }\\n if (enteringProtection) {\\n state.currentlyUsingProtectedPrice = true;\\n }\\n emit ProtectionTriggered(asset, spot, state.minPrice, state.maxPrice);\\n return true;\\n }\\n if (state.currentlyUsingProtectedPrice) return true;\\n }\\n\\n /**\\n * @notice Resolves the final bounded collateral and debt prices given a spot, window bounds, and protection flag.\\n * @dev When protection is active: collateral = min(spot, windowMin), debt = max(spot, windowMax).\\n * When protection is inactive: both return spot.\\n * @param protectionActive Whether the market protection window is currently active\\n * @param spot The current spot price\\n * @param windowMin The lower bound of the price window\\n * @param windowMax The upper bound of the price window\\n * @return minPrice The resolved lower-bound (collateral) price\\n * @return maxPrice The resolved upper-bound (debt) price\\n */\\n function _resolveBoundedPrices(\\n bool protectionActive,\\n uint256 spot,\\n uint256 windowMin,\\n uint256 windowMax\\n ) internal pure returns (uint256, uint256) {\\n if (!protectionActive) return (spot, spot);\\n return (spot < windowMin ? spot : windowMin, spot > windowMax ? spot : windowMax);\\n }\\n\\n /**\\n * @notice Shared view logic for all bounded price view functions.\\n * Checks transient cache first for an early return; on miss, fetches from oracle\\n * and computes both prices without state mutations.\\n * @param vToken vToken address\\n * @return minPrice The bounded lower (collateral) price\\n * @return maxPrice The bounded upper (debt) price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function _computeBoundedPrices(address vToken) internal view returns (uint256 minPrice, uint256 maxPrice) {\\n address asset = _getUnderlyingAsset(vToken);\\n\\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\\n (minPrice, maxPrice) = _getCachedPrices(asset);\\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\\n\\n // Cache miss \\u2014 fetch from oracle and compute without state mutations\\n uint256 spot = _fetchSpotPrice(asset);\\n MarketProtectionState storage state = assetProtectionConfig[asset];\\n if (!state.isBoundedPricingEnabled) return (spot, spot);\\n\\n // Mirror _expandPriceWindow logic: compute what the window would be after expansion\\n uint128 spotU128 = _safeToUint128(spot);\\n uint128 windowMin128 = spot < uint256(state.minPrice) ? spotU128 : state.minPrice;\\n uint128 windowMax128 = spot > uint256(state.maxPrice) ? spotU128 : state.maxPrice;\\n\\n bool shouldProtect = state.currentlyUsingProtectedPrice ||\\n _exceedsDeviationThreshold(spot, windowMin128, windowMax128, state.triggerThreshold);\\n\\n (minPrice, maxPrice) = _resolveBoundedPrices(shouldProtect, spot, uint256(windowMin128), uint256(windowMax128));\\n }\\n\\n /**\\n * @dev Computes the relative spread between the price window bounds as a ratio scaled by EXP_SCALE.\\n * Formula: \\\\((maxPrice - minPrice) / minPrice\\\\), scaled by `EXP_SCALE`.\\n * Used to measure how much the window has converged -- compared against `resetThreshold`\\n * to determine whether the price window is tight enough to exit protection mode.\\n * @param minPrice The minimum price in the window\\n * @param maxPrice The maximum price in the window\\n * @return The scaled bound ratio \\\\(((max - min) * EXP_SCALE) / min\\\\)\\n */\\n function _computePriceBoundRatio(uint128 minPrice, uint128 maxPrice) internal pure returns (uint256) {\\n uint256 range = uint256(maxPrice) - uint256(minPrice);\\n return (range * EXP_SCALE) / uint256(minPrice);\\n }\\n\\n /**\\n * @notice Checks whether the spot price has moved beyond the threshold relative to the\\n * opposite window bound \\u2014 i.e. `spot > minPrice * (1 + threshold)` or\\n * `spot < maxPrice * (1 - threshold)`.\\n * @dev Pump detection: spot > minPrice * (1 + threshold)\\n * Crash detection: spot < maxPrice * (1 - threshold)\\n * @param spot The current spot price\\n * @param minPrice The minimum price in the window\\n * @param maxPrice The maximum price in the window\\n * @param threshold The deviation threshold (mantissa)\\n * @return True if deviation is triggered\\n */\\n function _exceedsDeviationThreshold(\\n uint256 spot,\\n uint128 minPrice,\\n uint128 maxPrice,\\n uint256 threshold\\n ) internal pure returns (bool) {\\n uint256 upperBound = (uint256(minPrice) * (EXP_SCALE + threshold)) / EXP_SCALE;\\n uint256 lowerBound = (uint256(maxPrice) * (EXP_SCALE - threshold)) / EXP_SCALE;\\n return (spot > upperBound || spot < lowerBound);\\n }\\n\\n /**\\n * @dev Returns true if the relative drift between onChain and proposed exceeds KEEPER_DEADBAND\\n * @param currentPrice The current on-chain price\\n * @param proposedPrice The keeper's proposed price\\n * @return True if drift exceeds deadband\\n */\\n function _exceedsCorrectionDeadband(uint128 currentPrice, uint128 proposedPrice) internal pure returns (bool) {\\n if (currentPrice == 0 || proposedPrice == 0) return false;\\n uint256 diff = currentPrice > proposedPrice\\n ? uint256(currentPrice - proposedPrice)\\n : uint256(proposedPrice - currentPrice);\\n return (diff * EXP_SCALE) / uint256(currentPrice) > KEEPER_DEADBAND;\\n }\\n\\n /**\\n * @dev Sets the minimum price in the window and emits MinPriceUpdated\\n * @param state The market protection state\\n * @param asset The underlying asset address (for event emission)\\n * @param newMin The new minimum price\\n */\\n function _setMinPrice(MarketProtectionState storage state, address asset, uint128 newMin) internal {\\n emit MinPriceUpdated(asset, state.minPrice, newMin);\\n state.minPrice = newMin;\\n }\\n\\n /**\\n * @dev Sets the maximum price in the window and emits MaxPriceUpdated\\n * @param state The market protection state\\n * @param asset The underlying asset address (for event emission)\\n * @param newMax The new maximum price\\n */\\n function _setMaxPrice(MarketProtectionState storage state, address asset, uint128 newMax) internal {\\n emit MaxPriceUpdated(asset, state.maxPrice, newMax);\\n state.maxPrice = newMax;\\n }\\n\\n /**\\n * @dev Writes both lower and upper bounded prices to transient storage. No-ops when the\\n * asset's `cachingEnabled` flag is `false`, so callers that disable caching always\\n * fall through to live recomputation on subsequent reads.\\n * @param asset The underlying asset address\\n * @param minPrice The resolved lower (collateral) price to cache\\n * @param maxPrice The resolved upper (debt) price to cache\\n */\\n function _setCachedPrices(address asset, uint256 minPrice, uint256 maxPrice) internal {\\n if (!assetProtectionConfig[asset].cachingEnabled) return;\\n Transient.cachePrice(COLLATERAL_PRICE_CACHE_SLOT, asset, minPrice);\\n Transient.cachePrice(DEBT_PRICE_CACHE_SLOT, asset, maxPrice);\\n }\\n\\n /**\\n * @dev Reads a cached final price from transient storage. Returns `(0, 0)` when the\\n * asset's `cachingEnabled` flag is `false`, which callers already treat as a cache\\n * miss and handle via live recomputation.\\n * @param asset The underlying asset address\\n * @return minPrice The cached minimum price, or 0 on cache miss\\n * @return maxPrice The cached maximum price, or 0 on cache miss\\n */\\n function _getCachedPrices(address asset) internal view returns (uint256 minPrice, uint256 maxPrice) {\\n if (!assetProtectionConfig[asset].cachingEnabled) return (0, 0);\\n minPrice = Transient.readCachedPrice(COLLATERAL_PRICE_CACHE_SLOT, asset);\\n maxPrice = Transient.readCachedPrice(DEBT_PRICE_CACHE_SLOT, asset);\\n }\\n\\n /**\\n * @dev This function returns the underlying asset of a vToken\\n * @param vToken vToken address\\n * @return asset underlying asset address\\n */\\n function _getUnderlyingAsset(address vToken) private view returns (address asset) {\\n ensureNonzeroAddress(vToken);\\n if (vToken == nativeMarket) {\\n asset = NATIVE_TOKEN_ADDR;\\n } else if (vToken == vai) {\\n asset = vai;\\n } else {\\n asset = VBep20Interface(vToken).underlying();\\n }\\n }\\n\\n /**\\n * @dev Reverts if the market has not been initialized via setTokenConfig\\n * @param asset The underlying asset address\\n * @return state The market protection state storage pointer\\n */\\n function _ensureInitialized(address asset) internal view returns (MarketProtectionState storage state) {\\n state = assetProtectionConfig[asset];\\n if (state.asset == address(0)) revert MarketNotInitialized(asset);\\n }\\n\\n /**\\n * @notice Fetches the current spot price for an asset from the ResilientOracle\\n * @param asset The underlying asset address\\n * @return The current spot price\\n */\\n function _fetchSpotPrice(address asset) internal view returns (uint256) {\\n return RESILIENT_ORACLE.getPrice(asset);\\n }\\n\\n /**\\n * @dev Safely casts a uint256 to uint128, reverting on overflow\\n * @param value The value to cast\\n * @return The value as uint128\\n */\\n function _safeToUint128(uint256 value) internal pure returns (uint128) {\\n if (value > type(uint128).max) revert PriceExceedsUint128(value);\\n return uint128(value);\\n }\\n}\\n\",\"keccak256\":\"0x753d4b0caf8d71739c8615b1cdcc751f45576182ab82cd5859102c16e4402e0d\",\"license\":\"BSD-3-Clause\"},\"contracts/interfaces/IDeviationBoundedOracle.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\ninterface IDeviationBoundedOracle {\\n // --- Enums ---\\n\\n /// @notice Identifies whether a price bound is a minimum or maximum\\n enum PriceBoundType {\\n MIN,\\n MAX\\n }\\n\\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\\n enum KeeperAction {\\n SetMinPrice,\\n SetMaxPrice,\\n ExitProtectionMode\\n }\\n\\n // --- Structs ---\\n\\n /// @notice Per-asset protection state tracking the min/max price window\\n struct MarketProtectionState {\\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\\n uint128 minPrice;\\n /// @notice Highest price observed in the current window\\n uint128 maxPrice;\\n /// @notice Whether protected price is currently being used\\n bool currentlyUsingProtectedPrice;\\n /// @notice Whether this market is whitelisted for bounded pricing\\n bool isBoundedPricingEnabled;\\n /// @notice Timestamp of the last protection trigger \\u2014 reset on every trigger\\n uint64 lastProtectionTriggeredAt;\\n /// @notice Minimum time protection stays active after last trigger\\n uint64 cooldownPeriod;\\n /// @notice The underlying asset address, used to verify initialization\\n address asset;\\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\\n uint128 triggerThreshold;\\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\\n uint128 resetThreshold;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool cachingEnabled;\\n }\\n\\n /// @notice One item in an syncPriceBoundsAndProtections payload\\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\\n struct KeeperActionItem {\\n address asset;\\n KeeperAction action;\\n uint256 value;\\n }\\n\\n /// @notice One item in a setTokenConfigs payload\\n struct TokenConfigInput {\\n /// @notice The underlying asset address\\n address asset;\\n /// @notice Minimum time protection stays active after the last trigger (seconds)\\n uint64 cooldownPeriod;\\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\\n uint256 triggerThreshold;\\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\\n uint256 resetThreshold;\\n /// @notice Whether to enable bounded pricing immediately upon initialization\\n bool enableBoundedPricing;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool enableCaching;\\n }\\n\\n // --- Events ---\\n\\n /// @notice Emitted when protection is initialized for an asset\\n event ProtectionInitialized(\\n address indexed asset,\\n uint128 minPrice,\\n uint128 maxPrice,\\n uint64 cooldownPeriod,\\n uint256 triggerThreshold\\n );\\n\\n /// @notice Emitted when protection mode is triggered for an asset\\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\\n\\n /// @notice Emitted when protection mode is disabled for an asset\\n event ProtectionModeExited(address indexed asset);\\n\\n /// @notice Emitted when the keeper updates the minimum price for an asset\\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\\n\\n /// @notice Emitted when the keeper updates the maximum price for an asset\\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\\n\\n /// @notice Emitted when the entry threshold is updated for an asset\\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\\n\\n /// @notice Emitted when the exit threshold is updated for an asset\\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\\n\\n /// @notice Emitted when the cooldown period is updated for an asset\\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\\n\\n /// @notice Emitted when an asset's whitelist status changes\\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\\n\\n /// @notice Emitted when the per-asset transient caching flag is toggled\\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\\n\\n // --- Errors ---\\n\\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\\n error MarketNotInitialized(address asset);\\n\\n /// @notice Thrown when trying to initialize an already initialized market\\n error MarketAlreadyInitialized(address asset);\\n\\n /// @notice Thrown when trying to disable protection that is not active\\n error ProtectedPriceInactive(address asset);\\n\\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\\n\\n /// @notice Thrown when trying to disable protection before price range has converged\\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\\n\\n /// @notice Thrown when keeper tries to set minPrice above current spot\\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\\n\\n /// @notice Thrown when keeper tries to set maxPrice below current spot\\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\\n\\n /// @notice Thrown when threshold is set below the minimum allowed value\\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\\n\\n /// @notice Thrown when threshold is set above the maximum allowed value\\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\\n\\n /// @notice Thrown when a price exceeds uint128 max\\n error PriceExceedsUint128(uint256 price);\\n\\n /// @notice Thrown when a zero price is provided where a non-zero price is required\\n error ZeroPriceNotAllowed();\\n\\n /// @notice Thrown when trying to initialize protection for VAI\\n error VAINotAllowed();\\n\\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\\n error ProtectedPriceActive(address asset);\\n\\n /// @notice Thrown when the lengths of the arrays are not equal\\n error InvalidArrayLength();\\n\\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\\n error InvalidResetThreshold(uint256 resetThreshold);\\n\\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\\n error InvalidKeeperAction(uint8 action);\\n\\n // --- Non-view price functions (update window + trigger protection) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- State update (call before view price reads to populate transient cache) ---\\n\\n /**\\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\\n * reads in the same transaction are served from transient storage. The transient\\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\\n * @param vToken vToken address\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function updateProtectionState(address vToken) external;\\n\\n // --- View price functions (read stored/cached state only) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- Keeper functions ---\\n\\n /**\\n * @notice Updates the minimum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMin is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\\n * @custom:event MinPriceUpdated\\n */\\n function updateMinPrice(address asset, uint128 newMin) external;\\n\\n /**\\n * @notice Updates the maximum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMax is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\\n * @custom:event MaxPriceUpdated\\n */\\n function updateMaxPrice(address asset, uint128 newMax) external;\\n\\n /**\\n * @notice Exits protection mode for a given asset once conditions are met\\n * @param asset The underlying asset address\\n * @custom:access Only authorized monitor/keeper addresses\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\\n * @custom:event ProtectionModeExited\\n */\\n function exitProtectionMode(address asset) external;\\n\\n /**\\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\\n * Empty `actions` is a no-op success.\\n * @param actions The list of keeper actions to apply\\n * @custom:access Only authorized keeper addresses\\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\\n */\\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\\n\\n // --- Admin functions (governance-gated) ---\\n\\n /**\\n * @notice Initializes protection parameters for a new asset\\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\\n * confirming the oracle is live for this asset before it is listed.\\n * @param tokenConfig_ Token config input for the asset\\n * @custom:access Only Governance\\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\\n * @custom:error VAINotAllowed if asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event ProtectionInitialized\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\\n\\n /**\\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\\n * @param tokenConfigs_ Array of token config inputs, one per asset\\n * @custom:access Only Governance\\n * @custom:error InvalidArrayLength if the input array is empty\\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\\n * @custom:error VAINotAllowed if any asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\\n * @custom:event ProtectionInitialized for each asset\\n * @custom:event BoundedPricingWhitelistUpdated for each asset\\n */\\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\\n\\n /**\\n * @notice Sets the cooldown period for an asset\\n * @param asset The underlying asset address\\n * @param newCooldown The new cooldown period in seconds; must be non-zero\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CooldownPeriodSet\\n */\\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\\n\\n /**\\n * @notice Sets the trigger and reset thresholds for an asset\\n * @param asset The underlying asset address\\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\\n * @custom:event TriggerThresholdSet if the trigger threshold changed\\n * @custom:event ResetThresholdSet if the reset threshold changed\\n */\\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\\n\\n /**\\n * @notice Sets whether bounded pricing is enabled for an asset\\n * @param asset The underlying asset address\\n * @param enabled Whether bounded pricing should be enabled for the asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\\n\\n /**\\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\\n * live spot instead of reading or writing the transient slots. The initial value is\\n * set via the `enableCaching` argument of `setTokenConfig`.\\n * @param asset The underlying asset address\\n * @param enabled Whether transient caching is enabled for this asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CachingEnabledUpdated\\n */\\n function setCachingEnabled(address asset, bool enabled) external;\\n\\n // --- View helpers ---\\n\\n /**\\n * @notice Returns the full protection state for an asset\\n * @param asset The underlying asset address\\n * @return minPrice Lowest price observed in the current window\\n * @return maxPrice Highest price observed in the current window\\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\\n * @return cooldownPeriod Minimum time protection stays active after last trigger\\n * @return assetAddr The underlying asset address stored in the struct\\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\\n */\\n function assetProtectionConfig(\\n address asset\\n )\\n external\\n view\\n returns (\\n uint128 minPrice,\\n uint128 maxPrice,\\n bool currentlyUsingProtectedPrice,\\n bool isBoundedPricingEnabled,\\n uint64 lastProtectionTriggeredAt,\\n uint64 cooldownPeriod,\\n address assetAddr,\\n uint128 triggerThreshold,\\n uint128 resetThreshold,\\n bool cachingEnabled\\n );\\n\\n /**\\n * @notice Checks if an asset is whitelisted for bounded pricing\\n * @param asset The underlying asset address\\n * @return True if the asset is whitelisted\\n */\\n function isBoundedPricingEnabled(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if the asset is currently using the protected (bounded) price\\n * @param asset The underlying asset address\\n * @return True if the asset is currently using the protected price instead of spot\\n */\\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if protection can be exited for a given asset\\n * @param asset The underlying asset address\\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\\n */\\n function canExitProtection(address asset) external view returns (bool);\\n\\n /**\\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\\n * @param index Array index\\n * @return The asset address at the given index\\n */\\n function allAssets(uint256 index) external view returns (address);\\n\\n /**\\n * @notice Returns all currently whitelisted asset addresses\\n * @return result Array of whitelisted asset addresses\\n */\\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\\n\\n /**\\n * @notice Returns all asset addresses that have ever been initialized\\n * @return Array of all initialized asset addresses\\n */\\n function getInitializedAssets() external view returns (address[] memory);\\n\\n /**\\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\\n * @param assets Array of asset addresses to check\\n * @param proposedMins Keeper's proposed window minimum prices\\n * @param proposedMaxs Keeper's proposed window maximum prices\\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\\n * @custom:error InvalidArrayLength if the input array lengths do not match\\n */\\n function checkAndGetWindowDrift(\\n address[] calldata assets,\\n uint128[] calldata proposedMins,\\n uint128[] calldata proposedMaxs\\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\\n}\\n\",\"keccak256\":\"0xe223d88c17c0d752fdb33fa223b63ce124a798512f3d69f3800e9508e7dff931\",\"license\":\"BSD-3-Clause\"},\"contracts/interfaces/OracleInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\ninterface OracleInterface {\\n function getPrice(address asset) external view returns (uint256);\\n}\\n\\ninterface ResilientOracleInterface is OracleInterface {\\n function updatePrice(address vToken) external;\\n\\n function updateAssetPrice(address asset) external;\\n\\n function getUnderlyingPrice(address vToken) external view returns (uint256);\\n}\\n\\ninterface BoundValidatorInterface {\\n function validatePriceWithAnchorPrice(\\n address asset,\\n uint256 reporterPrice,\\n uint256 anchorPrice\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xd3bbb7c9eef19e8f467342df6034ef95399a00964646fb8c82b438968ae3a8c0\",\"license\":\"BSD-3-Clause\"},\"contracts/interfaces/VBep20Interface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\ninterface VBep20Interface is IERC20Metadata {\\n /**\\n * @notice Underlying asset for this VToken\\n */\\n function underlying() external view returns (address);\\n}\\n\",\"keccak256\":\"0x6e71c3df86501df5c0e4bace1333c0c91f9f9cced252a54fb99eeda219b789d5\",\"license\":\"BSD-3-Clause\"},\"contracts/lib/Transient.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nlibrary Transient {\\n /**\\n * @notice Cache the asset price into transient storage\\n * @param key address of the asset\\n * @param value asset price\\n */\\n function cachePrice(bytes32 cacheSlot, address key, uint256 value) internal {\\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\\n assembly (\\\"memory-safe\\\") {\\n tstore(slot, value)\\n }\\n }\\n\\n /**\\n * @notice Read cached price from transient storage\\n * @param key address of the asset\\n * @return value cached asset price\\n */\\n function readCachedPrice(bytes32 cacheSlot, address key) internal view returns (uint256 value) {\\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\\n assembly (\\\"memory-safe\\\") {\\n value := tload(slot)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x60d7133a48a757ee777cb9230e890ef489ffc33dcea9dadfcf5a8b72f9dd43aa\",\"license\":\"BSD-3-Clause\"}},\"version\":1}", + "bytecode": "0x60e060405234801561000f575f80fd5b5060405161345438038061345483398101604081905261002e91610163565b61003783610069565b61004082610069565b6001600160a01b0380841660805282811660a052811660c052610061610093565b5050506101ad565b6001600160a01b038116610090576040516342bcdf7f60e11b815260040160405180910390fd5b50565b5f54610100900460ff16156100fe5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b5f5460ff9081161461014d575f805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6001600160a01b0381168114610090575f80fd5b5f805f60608486031215610175575f80fd5b83516101808161014f565b60208501519093506101918161014f565b60408501519092506101a28161014f565b809150509250925092565b60805160a05160c05161325a6101fa5f395f818161058501528181611c610152818161258001526125ba01525f818161048d015261252c01525f81816104fe01526119d2015261325a5ff3fe608060405234801561000f575f80fd5b5060043610610255575f3560e01c806388142b6b11610140578063b540894f116100bf578063cf1412c011610084578063cf1412c0146106ba578063dcc88962146106cd578063e2201bb8146106e0578063e30c3978146106f3578063e40c2fed14610704578063f2fde38b14610717575f80fd5b8063b540894f1461055f578063b62e4c9214610580578063bd11c4c0146103fe578063c3821757146105a7578063c4d66de8146106a7575f80fd5b8063a31ebe5811610105578063a31ebe58146104e6578063a4edcd4c146104f9578063a9534f8a14610520578063a9c3cab11461053b578063b4a0bdf31461054e575f80fd5b806388142b6b146104755780638a2f7f6d146104885780638cf38bb1146104af5780638da5cb5b146104c2578063969f58d3146104d3575f80fd5b80634912c452116101d757806376489e381161019c57806376489e38146103b357806379ba5097146103f65780637f5e1328146103fe5780637fa6ea501461040c578063870dc597146104345780638769b23114610447575f80fd5b80634912c4521461034757806352a6bce31461035a578063578e5c221461036d5780636121316814610398578063715018a6146103ab575f80fd5b80631be74faf1161021d5780631be74faf146102f157806323013b83146103065780632f7b5dd7146103195780633b4ecdb21461032c578063412b6ec714610334575f80fd5b806308af5431146102595780630a5fa04d1461027b5780630e32cb86146102a25780631169d5a6146102b757806315a53122146102de575b5f80fd5b6102686706f05b59d3b2000081565b6040519081526020015b60405180910390f35b6102687f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb897060081565b6102b56102b0366004612b01565b61072a565b005b6102687f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf700081565b6102b56102ec366004612b32565b61073e565b6102f961078b565b6040516102729190612b65565b6102b5610314366004612bbe565b61093e565b6102b5610327366004612bf5565b610a6a565b6102f9610b33565b6102b5610342366004612bbe565b610b93565b6102b5610355366004612c63565b610c43565b610268610368366004612b01565b610e3c565b61038061037b366004612c95565b610e4d565b6040516001600160a01b039091168152602001610272565b6102b56103a6366004612cac565b610e75565b6102b5610ea6565b6103e66103c1366004612b01565b6001600160a01b03165f90815260c96020526040902060010154610100900460ff1690565b6040519015158152602001610272565b6102b5610eb9565b61026866b1a2bc2ec5000081565b61041f61041a366004612b01565b610f30565b60408051928352602083019190915201610272565b6102b5610442366004612cc2565b610f44565b6103e6610455366004612b01565b6001600160a01b03165f90815260c9602052604090206001015460ff1690565b61041f610483366004612b01565b6110af565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b6102b56104bd366004612b01565b6110ba565b6033546001600160a01b0316610380565b6102b56104e1366004612b32565b611101565b6102b56104f4366004612d34565b61114b565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b61038073bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb81565b6102b5610549366004612b01565b61121e565b6097546001600160a01b0316610380565b61057261056d366004612da5565b61122c565b604051610272929190612e73565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b6106326105b5366004612b01565b60c96020525f9081526040902080546001820154600283015460038401546004909401546001600160801b0380851695600160801b9586900482169560ff8087169661010081048216966001600160401b03620100008304811697600160501b90930416956001600160a01b03909116948082169490041691168a565b604080516001600160801b039b8c168152998b1660208b01529715159789019790975294151560608801526001600160401b0393841660808801529290911660a08601526001600160a01b031660c0850152841660e08401529290921661010082015290151561012082015261014001610272565b6102b56106b5366004612b01565b6113f2565b6102686106c8366004612b01565b6114ff565b6103e66106db366004612b01565b611510565b6102686106ee366004612b01565b61159c565b6065546001600160a01b0316610380565b610268610712366004612b01565b6115a6565b6102b5610725366004612b01565b6115b0565b610732611621565b61073b8161167b565b50565b61077c6040518060400160405280601f81526020017f7570646174654d696e507269636528616464726573732c75696e743132382900815250611739565b61078782825f6117d0565b5050565b60ca546060905f816001600160401b038111156107aa576107aa612ea0565b6040519080825280602002602001820160405280156107d3578160200160208202803683370190505b5090505f805b838110156108995760c95f60ca83815481106107f7576107f7612eb4565b5f9182526020808320909101546001600160a01b0316835282019290925260400190206001015460ff61010090910416156108915760ca818154811061083f5761083f612eb4565b5f918252602090912001546001600160a01b0316838361085e81612edc565b94508151811061087057610870612eb4565b60200260200101906001600160a01b031690816001600160a01b0316815250505b6001016107d9565b505f816001600160401b038111156108b3576108b3612ea0565b6040519080825280602002602001820160405280156108dc578160200160208202803683370190505b5090505f5b82811015610935578381815181106108fb576108fb612eb4565b602002602001015182828151811061091557610915612eb4565b6001600160a01b03909216602092830291909101909101526001016108e1565b50949350505050565b61095f6040518060600160405280602b8152602001613104602b9139611739565b6109688261193d565b5f61097283611964565b9050811580156109865750600181015460ff165b156109b4576040516310ce5af160e11b81526001600160a01b03841660048201526024015b60405180910390fd5b8115158160010160019054906101000a900460ff161515036109d557505050565b8115610a07575f6109ed6109e8856119b1565b611a43565b90506109fa828583611a73565b610a05828583611ae3565b505b6001810180548315156101000261ff00199091161790556040516001600160a01b038416907fde52a1c70ed1ca343b25cca640873d949641bd6ec7a2c2d0e67374ce54ff89cd90610a5d90851515815260200190565b60405180910390a2505050565b610a8b6040518060600160405280603d815260200161312f603d9139611739565b805f819003610aad57604051634ec4810560e11b815260040160405180910390fd5b5f5b81811015610b2d5736848483818110610aca57610aca612eb4565b60c002919091019150610b249050610ae56020830183612b01565b610af56040840160208501612ef4565b60408401356060850135610b0f60a0870160808801612f0d565b610b1f60c0880160a08901612f0d565b611b57565b50600101610aaf565b50505050565b606060ca805480602002602001604051908101604052809291908181526020018280548015610b8957602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311610b6b575b5050505050905090565b610bd16040518060400160405280601f81526020017f73657443616368696e67456e61626c656428616464726573732c626f6f6c2900815250611739565b5f610bdb83611964565b60048101546040805160ff9092161515825284151560208301529192506001600160a01b038516917f80b87a057217c1d7743a43adf6fcf8a06553fde1205440ebbfb539f6f0e04424910160405180910390a2600401805460ff191691151591909117905550565b610c6460405180606001604052806026815260200161316c60269139611739565b610c6d8361193d565b610c768261200e565b610c7f8161200e565b66b1a2bc2ec50000821015610cb757604051630f4d736160e01b81526004810183905266b1a2bc2ec5000060248201526044016109ab565b6706f05b59d3b20000821115610cf1576040516350ddbb7560e11b8152600481018390526706f05b59d3b2000060248201526044016109ab565b818110610d1457604051632021db5560e21b8152600481018290526024016109ab565b5f610d1e84611964565b60038101549091506001600160801b03168314610da6576003810154604080516001600160801b039092168252602082018590526001600160a01b038616917f605baae98875f2133a40a8d489531e195b473e8cdb0c860a2d2b51d430eff588910160405180910390a26003810180546001600160801b0319166001600160801b0385161790555b6003810154600160801b90046001600160801b03168214610b2d57600381015460408051600160801b9092046001600160801b03168252602082018490526001600160a01b038616917f75c087963c80520a0d86e6333991c95d79462341f6960f2904396c23def23dd4910160405180910390a26003810180546001600160801b03808516600160801b02911617905550505050565b5f610e468261202e565b9392505050565b60ca8181548110610e5c575f80fd5b5f918252602090912001546001600160a01b0316905081565b610e966040518060600160405280603a8152602001613192603a9139611739565b61073b610ae56020830183612b01565b610eae611621565b610eb75f61210c565b565b60655433906001600160a01b03168114610f275760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016109ab565b61073b8161210c565b5f80610f3b8361202e565b91509150915091565b610f656040518060600160405280603881526020016131cc60389139611739565b805f5b81811015610b2d5736848483818110610f8357610f83612eb4565b6060029190910191505f9050610f9f6040830160208401612f3c565b6002811115610fb057610fb0612f28565b03610fdd57610fd8610fc56020830183612b01565b610fd28360400135611a43565b5f6117d0565b6110a6565b6001610fef6040830160208401612f3c565b600281111561100057611000612f28565b0361102957610fd86110156020830183612b01565b6110228360400135611a43565b60016117d0565b600261103b6040830160208401612f3c565b600281111561104c5761104c612f28565b0361106657610fd86110616020830183612b01565b612125565b6110766040820160208301612f3c565b600281111561108757611087612f28565b604051635374467d60e11b815260ff90911660048201526024016109ab565b50600101610f68565b5f80610f3b836122b9565b6110f86040518060400160405280601b81526020017f6578697450726f74656374696f6e4d6f64652861646472657373290000000000815250611739565b61073b81612125565b61113f6040518060400160405280601f81526020017f7570646174654d6178507269636528616464726573732c75696e743132382900815250611739565b610787828260016117d0565b61116c60405180606001604052806021815260200161320460219139611739565b6111758261193d565b611187816001600160401b031661200e565b5f61119183611964565b6001810154604080516001600160401b03600160501b9093048316815291851660208301529192506001600160a01b038516917f890c8f3ce148a0c50a68d44c085aa3228b8e72273a03dba4ecfd402f94033c1e910160405180910390a260010180546001600160401b03909216600160501b0267ffffffffffffffff60501b1990921691909117905550565b6112278161202e565b505050565b60608086858114158061123f5750808414155b1561125d57604051634ec4810560e11b815260040160405180910390fd5b806001600160401b0381111561127557611275612ea0565b60405190808252806020026020018201604052801561129e578160200160208202803683370190505b509250806001600160401b038111156112b9576112b9612ea0565b6040519080825280602002602001820160405280156112e2578160200160208202803683370190505b5091505f5b818110156113e5575f60c95f8c8c8581811061130557611305612eb4565b905060200201602081019061131a9190612b01565b6001600160a01b0316815260208101919091526040015f208054909150611370906001600160801b03168a8a8581811061135657611356612eb4565b905060200201602081019061136b9190612f5a565b6123f8565b85838151811061138257611382612eb4565b9115156020928302919091019091015280546113ba90600160801b90046001600160801b031688888581811061135657611356612eb4565b8483815181106113cc576113cc612eb4565b91151560209283029190910190910152506001016112e7565b5050965096945050505050565b5f54610100900460ff161580801561141057505f54600160ff909116105b806114295750303b15801561142957505f5460ff166001145b61148c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016109ab565b5f805460ff1916600117905580156114ad575f805461ff0019166101001790555b6114b6826124a1565b8015610787575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15050565b5f6115098261202e565b5092915050565b6001600160a01b0381165f90815260c960205260408120600181015460ff1680156115615750600181015461155d906001600160401b03600160501b820481169162010000900416612f73565b4210155b8015610e465750600381015481546001600160801b03600160801b928390048116926115949280831692919004166124d8565b109392505050565b5f610e46826122b9565b5f611509826122b9565b6115b8611621565b606580546001600160a01b0383166001600160a01b031990911681179091556115e96033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6033546001600160a01b03163314610eb75760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109ab565b6001600160a01b0381166116df5760405162461bcd60e51b815260206004820152602560248201527f696e76616c696420616365737320636f6e74726f6c206d616e61676572206164604482015264647265737360d81b60648201526084016109ab565b609780546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa091016114f3565b6097546040516318c5e8ab60e01b81525f916001600160a01b0316906318c5e8ab9061176b9033908690600401612fb4565b602060405180830381865afa158015611786573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117aa9190612fd7565b90508061078757333083604051634a3fa29360e01b81526004016109ab93929190612ff2565b6117d98361193d565b816001600160801b03165f036118025760405163a2b326dd60e01b815260040160405180910390fd5b5f61180c84611964565b90505f611818856119b1565b90505f83600181111561182d5761182d612f28565b036118af5781546001600160801b03600160801b9091048116908516118061185d575080846001600160801b0316115b1561189f5760405160016203590160e31b031981526001600160a01b03861660048201526001600160801b0385166024820152604481018290526064016109ab565b6118aa828686611a73565b611936565b60018360018111156118c3576118c3612f28565b036119365781546001600160801b0390811690851610806118ec575080846001600160801b0316105b1561192b57604051636df018fb60e11b81526001600160a01b03861660048201526001600160801b0385166024820152604481018290526064016109ab565b611936828686611ae3565b5050505050565b6001600160a01b03811661073b576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160a01b038082165f90815260c96020526040902060028101549091166119ac576040516349c9b68960e11b81526001600160a01b03831660048201526024016109ab565b919050565b6040516341976e0960e01b81526001600160a01b0382811660048301525f917f0000000000000000000000000000000000000000000000000000000000000000909116906341976e0990602401602060405180830381865afa158015611a19573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a3d919061301d565b92915050565b5f6001600160801b03821115611a6f5760405163339718d960e11b8152600481018390526024016109ab565b5090565b8254604080516001600160801b03928316815291831660208301526001600160a01b038416917f532579a79f45c3e02b749f0817d46bab6b256e8d1dae660d2f432d8032f21f2d910160405180910390a282546001600160801b0319166001600160801b03919091161790915550565b825460408051600160801b9092046001600160801b039081168352831660208301526001600160a01b038416917f067a69521c594f269500a8d1fd306d6cade49e09512a3fdf637a16aa1694dd88910160405180910390a282546001600160801b03918216600160801b0291161790915550565b611b608661193d565b611b72856001600160401b031661200e565b611b7b8461200e565b611b848361200e565b6001600160a01b038681165f90815260c960205260409020600201541615611bca57604051630d8cdd9d60e01b81526001600160a01b03871660048201526024016109ab565b66b1a2bc2ec50000841015611c0257604051630f4d736160e01b81526004810185905266b1a2bc2ec5000060248201526044016109ab565b6706f05b59d3b20000841115611c3c576040516350ddbb7560e11b8152600481018590526706f05b59d3b2000060248201526044016109ab565b838310611c5f57604051632021db5560e21b8152600481018490526024016109ab565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316866001600160a01b031603611cb15760405163c992a64b60e01b815260040160405180910390fd5b5f611cbe6109e8886119b1565b9050604051806101400160405280826001600160801b03168152602001826001600160801b031681526020015f1515815260200184151581526020015f6001600160401b03168152602001876001600160401b03168152602001886001600160a01b03168152602001866001600160801b03168152602001856001600160801b0316815260200183151581525060c95f896001600160a01b03166001600160a01b031681526020019081526020015f205f820151815f015f6101000a8154816001600160801b0302191690836001600160801b031602179055506020820151815f0160106101000a8154816001600160801b0302191690836001600160801b031602179055506040820151816001015f6101000a81548160ff02191690831515021790555060608201518160010160016101000a81548160ff02191690831515021790555060808201518160010160026101000a8154816001600160401b0302191690836001600160401b0316021790555060a082015181600101600a6101000a8154816001600160401b0302191690836001600160401b0316021790555060c0820151816002015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060e0820151816003015f6101000a8154816001600160801b0302191690836001600160801b031602179055506101008201518160030160106101000a8154816001600160801b0302191690836001600160801b03160217905550610120820151816004015f6101000a81548160ff02191690831515021790555090505060ca87908060018154018082558091505060019003905f5260205f20015f9091909190916101000a8154816001600160a01b0302191690836001600160a01b03160217905550866001600160a01b03167feaeb53cd979b528911d7ed793f11a25e44e5076bef70f458f60049acb4c322b682838989604051611fb894939291906001600160801b0394851681529290931660208301526001600160401b03166040820152606081019190915260800190565b60405180910390a2866001600160a01b03167fde52a1c70ed1ca343b25cca640873d949641bd6ec7a2c2d0e67374ce54ff89cd84604051611ffd911515815260200190565b60405180910390a250505050505050565b805f0361073b5760405163273e150360e21b815260040160405180910390fd5b5f805f61203a84612520565b90506120458161263e565b9093509150821580159061205857508115155b156120635750915091565b5f61206d826119b1565b6001600160a01b0383165f90815260c960205260409020600181015491925090610100900460ff166120af576120a48383846126c9565b509485945092505050565b5f805f6120bd848688612746565b9250925092505f6120d0858789856127c7565b90506120f08187866001600160801b0316866001600160801b03166128ce565b9099509750612100878a8a6126c9565b50505050505050915091565b606580546001600160a01b031916905561073b8161290b565b61212e8161193d565b5f61213882611964565b600181015490915060ff1661216b57604051638e001e1f60e01b81526001600160a01b03831660048201526024016109ab565b6001810154612192906001600160401b03600160501b820481169162010000900416612f73565b4210156121e7576001810154604051630874f3a160e01b81526001600160a01b03841660048201526001600160401b0362010000830481166024830152600160501b90920490911660448201526064016109ab565b80545f90612208906001600160801b0380821691600160801b9004166124d8565b6003830154909150600160801b90046001600160801b0316811061226c5760038201546040516311c9371560e01b81526001600160a01b038516600482015260248101839052600160801b9091046001600160801b031660448201526064016109ab565b60018201805469ffffffffffffffff00ff191690556040516001600160a01b038416907f6977cf3ab0aaccc6ceeb77ddaf93b2e1f1b9e6c7e39c7fb08314efbbea365741905f90a2505050565b5f805f6122c584612520565b90506122d08161263e565b909350915082158015906122e357508115155b156122ee5750915091565b5f6122f8826119b1565b6001600160a01b0383165f90815260c960205260409020600181015491925090610100900460ff1661232f57509485945092505050565b5f61233983611a43565b82549091505f906001600160801b0316841061235f5782546001600160801b0316612361565b815b83549091505f90600160801b90046001600160801b03168511612395578354600160801b90046001600160801b0316612397565b825b60018501549091505f9060ff16806123c7575060038501546123c7908790859085906001600160801b031661295c565b90506123e78187856001600160801b0316856001600160801b03166128ce565b909b909a5098505050505050505050565b5f6001600160801b038316158061241657506001600160801b038216155b1561242257505f611a3d565b5f826001600160801b0316846001600160801b031611612454576124468484613034565b6001600160801b0316612468565b61245e8385613034565b6001600160801b03165b905066b1a2bc2ec500006001600160801b03851661248e670de0b6b3a764000084613054565b612498919061306b565b11949350505050565b5f54610100900460ff166124c75760405162461bcd60e51b81526004016109ab9061308a565b6124cf6129db565b61073b81612a09565b5f806124f06001600160801b038086169085166130d5565b90506001600160801b03841661250e670de0b6b3a764000083613054565b612518919061306b565b949350505050565b5f61252a8261193d565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361257e575073bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb919050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316036125de57507f0000000000000000000000000000000000000000000000000000000000000000919050565b816001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561261a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a3d91906130e8565b6001600160a01b0381165f90815260c96020526040812060040154819060ff1661266c57505f928392509050565b6126967f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf700084612a2f565b91506126c27f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb897060084612a2f565b9050915091565b6001600160a01b0383165f90815260c9602052604090206004015460ff166126f057505050565b61271b7f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf70008484612a77565b6112277f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb89706008483612a77565b5f805f8061275386611a43565b87549091506001600160801b0380821691600160801b90048116905f90841683111561278b576127848a8986611a73565b5082915060015b816001600160801b0316846001600160801b031611156127b7576127b08a8986611ae3565b5082905060015b9199909850909650945050505050565b835460038501545f916127f39186916001600160801b0380821692600160801b9092048116911661295c565b156128b657600185015460ff1615808061280a5750825b156128355760018601805469ffffffffffffffff0000191662010000426001600160401b0316021790555b801561284c576001868101805460ff191690911790555b8554604080518781526001600160801b038084166020830152600160801b909304909216908201526001600160a01b038516907f0e25941e7a04e3bd937c28c403c9431e5fe5cf87df19322b5352bf457f8b1c5f9060600160405180910390a26001915050612518565b600185015460ff161561251857506001949350505050565b5f80856128df575083905080612902565b8385106128ec57836128ee565b845b8386116128fb57836128fd565b855b915091505b94509492505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f80670de0b6b3a76400006129718482612f73565b612984906001600160801b038816613054565b61298e919061306b565b90505f670de0b6b3a76400006129a485826130d5565b6129b7906001600160801b038816613054565b6129c1919061306b565b9050818711806129d057508087105b979650505050505050565b5f54610100900460ff16612a015760405162461bcd60e51b81526004016109ab9061308a565b610eb7612abe565b5f54610100900460ff166107325760405162461bcd60e51b81526004016109ab9061308a565b5f808383604051602001612a569291909182526001600160a01b0316602082015260400190565b60408051601f1981840301815291905280516020909101205c949350505050565b5f8383604051602001612a9d9291909182526001600160a01b0316602082015260400190565b60405160208183030381529060405280519060200120905081815d50505050565b5f54610100900460ff16612ae45760405162461bcd60e51b81526004016109ab9061308a565b610eb73361210c565b6001600160a01b038116811461073b575f80fd5b5f60208284031215612b11575f80fd5b8135610e4681612aed565b80356001600160801b03811681146119ac575f80fd5b5f8060408385031215612b43575f80fd5b8235612b4e81612aed565b9150612b5c60208401612b1c565b90509250929050565b602080825282518282018190525f9190848201906040850190845b81811015612ba55783516001600160a01b031683529284019291840191600101612b80565b50909695505050505050565b801515811461073b575f80fd5b5f8060408385031215612bcf575f80fd5b8235612bda81612aed565b91506020830135612bea81612bb1565b809150509250929050565b5f8060208385031215612c06575f80fd5b82356001600160401b0380821115612c1c575f80fd5b818501915085601f830112612c2f575f80fd5b813581811115612c3d575f80fd5b86602060c083028501011115612c51575f80fd5b60209290920196919550909350505050565b5f805f60608486031215612c75575f80fd5b8335612c8081612aed565b95602085013595506040909401359392505050565b5f60208284031215612ca5575f80fd5b5035919050565b5f60c08284031215612cbc575f80fd5b50919050565b5f8060208385031215612cd3575f80fd5b82356001600160401b0380821115612ce9575f80fd5b818501915085601f830112612cfc575f80fd5b813581811115612d0a575f80fd5b866020606083028501011115612c51575f80fd5b80356001600160401b03811681146119ac575f80fd5b5f8060408385031215612d45575f80fd5b8235612d5081612aed565b9150612b5c60208401612d1e565b5f8083601f840112612d6e575f80fd5b5081356001600160401b03811115612d84575f80fd5b6020830191508360208260051b8501011115612d9e575f80fd5b9250929050565b5f805f805f8060608789031215612dba575f80fd5b86356001600160401b0380821115612dd0575f80fd5b612ddc8a838b01612d5e565b90985096506020890135915080821115612df4575f80fd5b612e008a838b01612d5e565b90965094506040890135915080821115612e18575f80fd5b50612e2589828a01612d5e565b979a9699509497509295939492505050565b5f815180845260208085019450602084015f5b83811015612e68578151151587529582019590820190600101612e4a565b509495945050505050565b604081525f612e856040830185612e37565b8281036020840152612e978185612e37565b95945050505050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f60018201612eed57612eed612ec8565b5060010190565b5f60208284031215612f04575f80fd5b610e4682612d1e565b5f60208284031215612f1d575f80fd5b8135610e4681612bb1565b634e487b7160e01b5f52602160045260245ffd5b5f60208284031215612f4c575f80fd5b813560038110610e46575f80fd5b5f60208284031215612f6a575f80fd5b610e4682612b1c565b80820180821115611a3d57611a3d612ec8565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190525f9061251890830184612f86565b5f60208284031215612fe7575f80fd5b8151610e4681612bb1565b6001600160a01b038481168252831660208201526060604082018190525f90612e9790830184612f86565b5f6020828403121561302d575f80fd5b5051919050565b6001600160801b0382811682821603908082111561150957611509612ec8565b8082028115828204841417611a3d57611a3d612ec8565b5f8261308557634e487b7160e01b5f52601260045260245ffd5b500490565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b81810381811115611a3d57611a3d612ec8565b5f602082840312156130f8575f80fd5b8151610e4681612aed56fe7365744173736574426f756e64656450726963696e67456e61626c656428616464726573732c626f6f6c29736574546f6b656e436f6e666967732828616464726573732c75696e7436342c75696e743235362c75696e743235362c626f6f6c2c626f6f6c295b5d297365745468726573686f6c647328616464726573732c75696e743235362c75696e7432353629736574546f6b656e436f6e6669672828616464726573732c75696e7436342c75696e743235362c75696e743235362c626f6f6c2c626f6f6c292973796e635072696365426f756e6473416e6450726f74656374696f6e732828616464726573732c75696e74382c75696e74323536295b5d29736574436f6f6c646f776e506572696f6428616464726573732c75696e74363429a264697066735822122007b4cdcf907b0609fb9db3ddc0f71466578874b106706d462bbd01df9551438864736f6c63430008190033", + "deployedBytecode": "0x608060405234801561000f575f80fd5b5060043610610255575f3560e01c806388142b6b11610140578063b540894f116100bf578063cf1412c011610084578063cf1412c0146106ba578063dcc88962146106cd578063e2201bb8146106e0578063e30c3978146106f3578063e40c2fed14610704578063f2fde38b14610717575f80fd5b8063b540894f1461055f578063b62e4c9214610580578063bd11c4c0146103fe578063c3821757146105a7578063c4d66de8146106a7575f80fd5b8063a31ebe5811610105578063a31ebe58146104e6578063a4edcd4c146104f9578063a9534f8a14610520578063a9c3cab11461053b578063b4a0bdf31461054e575f80fd5b806388142b6b146104755780638a2f7f6d146104885780638cf38bb1146104af5780638da5cb5b146104c2578063969f58d3146104d3575f80fd5b80634912c452116101d757806376489e381161019c57806376489e38146103b357806379ba5097146103f65780637f5e1328146103fe5780637fa6ea501461040c578063870dc597146104345780638769b23114610447575f80fd5b80634912c4521461034757806352a6bce31461035a578063578e5c221461036d5780636121316814610398578063715018a6146103ab575f80fd5b80631be74faf1161021d5780631be74faf146102f157806323013b83146103065780632f7b5dd7146103195780633b4ecdb21461032c578063412b6ec714610334575f80fd5b806308af5431146102595780630a5fa04d1461027b5780630e32cb86146102a25780631169d5a6146102b757806315a53122146102de575b5f80fd5b6102686706f05b59d3b2000081565b6040519081526020015b60405180910390f35b6102687f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb897060081565b6102b56102b0366004612b01565b61072a565b005b6102687f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf700081565b6102b56102ec366004612b32565b61073e565b6102f961078b565b6040516102729190612b65565b6102b5610314366004612bbe565b61093e565b6102b5610327366004612bf5565b610a6a565b6102f9610b33565b6102b5610342366004612bbe565b610b93565b6102b5610355366004612c63565b610c43565b610268610368366004612b01565b610e3c565b61038061037b366004612c95565b610e4d565b6040516001600160a01b039091168152602001610272565b6102b56103a6366004612cac565b610e75565b6102b5610ea6565b6103e66103c1366004612b01565b6001600160a01b03165f90815260c96020526040902060010154610100900460ff1690565b6040519015158152602001610272565b6102b5610eb9565b61026866b1a2bc2ec5000081565b61041f61041a366004612b01565b610f30565b60408051928352602083019190915201610272565b6102b5610442366004612cc2565b610f44565b6103e6610455366004612b01565b6001600160a01b03165f90815260c9602052604090206001015460ff1690565b61041f610483366004612b01565b6110af565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b6102b56104bd366004612b01565b6110ba565b6033546001600160a01b0316610380565b6102b56104e1366004612b32565b611101565b6102b56104f4366004612d34565b61114b565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b61038073bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb81565b6102b5610549366004612b01565b61121e565b6097546001600160a01b0316610380565b61057261056d366004612da5565b61122c565b604051610272929190612e73565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b6106326105b5366004612b01565b60c96020525f9081526040902080546001820154600283015460038401546004909401546001600160801b0380851695600160801b9586900482169560ff8087169661010081048216966001600160401b03620100008304811697600160501b90930416956001600160a01b03909116948082169490041691168a565b604080516001600160801b039b8c168152998b1660208b01529715159789019790975294151560608801526001600160401b0393841660808801529290911660a08601526001600160a01b031660c0850152841660e08401529290921661010082015290151561012082015261014001610272565b6102b56106b5366004612b01565b6113f2565b6102686106c8366004612b01565b6114ff565b6103e66106db366004612b01565b611510565b6102686106ee366004612b01565b61159c565b6065546001600160a01b0316610380565b610268610712366004612b01565b6115a6565b6102b5610725366004612b01565b6115b0565b610732611621565b61073b8161167b565b50565b61077c6040518060400160405280601f81526020017f7570646174654d696e507269636528616464726573732c75696e743132382900815250611739565b61078782825f6117d0565b5050565b60ca546060905f816001600160401b038111156107aa576107aa612ea0565b6040519080825280602002602001820160405280156107d3578160200160208202803683370190505b5090505f805b838110156108995760c95f60ca83815481106107f7576107f7612eb4565b5f9182526020808320909101546001600160a01b0316835282019290925260400190206001015460ff61010090910416156108915760ca818154811061083f5761083f612eb4565b5f918252602090912001546001600160a01b0316838361085e81612edc565b94508151811061087057610870612eb4565b60200260200101906001600160a01b031690816001600160a01b0316815250505b6001016107d9565b505f816001600160401b038111156108b3576108b3612ea0565b6040519080825280602002602001820160405280156108dc578160200160208202803683370190505b5090505f5b82811015610935578381815181106108fb576108fb612eb4565b602002602001015182828151811061091557610915612eb4565b6001600160a01b03909216602092830291909101909101526001016108e1565b50949350505050565b61095f6040518060600160405280602b8152602001613104602b9139611739565b6109688261193d565b5f61097283611964565b9050811580156109865750600181015460ff165b156109b4576040516310ce5af160e11b81526001600160a01b03841660048201526024015b60405180910390fd5b8115158160010160019054906101000a900460ff161515036109d557505050565b8115610a07575f6109ed6109e8856119b1565b611a43565b90506109fa828583611a73565b610a05828583611ae3565b505b6001810180548315156101000261ff00199091161790556040516001600160a01b038416907fde52a1c70ed1ca343b25cca640873d949641bd6ec7a2c2d0e67374ce54ff89cd90610a5d90851515815260200190565b60405180910390a2505050565b610a8b6040518060600160405280603d815260200161312f603d9139611739565b805f819003610aad57604051634ec4810560e11b815260040160405180910390fd5b5f5b81811015610b2d5736848483818110610aca57610aca612eb4565b60c002919091019150610b249050610ae56020830183612b01565b610af56040840160208501612ef4565b60408401356060850135610b0f60a0870160808801612f0d565b610b1f60c0880160a08901612f0d565b611b57565b50600101610aaf565b50505050565b606060ca805480602002602001604051908101604052809291908181526020018280548015610b8957602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311610b6b575b5050505050905090565b610bd16040518060400160405280601f81526020017f73657443616368696e67456e61626c656428616464726573732c626f6f6c2900815250611739565b5f610bdb83611964565b60048101546040805160ff9092161515825284151560208301529192506001600160a01b038516917f80b87a057217c1d7743a43adf6fcf8a06553fde1205440ebbfb539f6f0e04424910160405180910390a2600401805460ff191691151591909117905550565b610c6460405180606001604052806026815260200161316c60269139611739565b610c6d8361193d565b610c768261200e565b610c7f8161200e565b66b1a2bc2ec50000821015610cb757604051630f4d736160e01b81526004810183905266b1a2bc2ec5000060248201526044016109ab565b6706f05b59d3b20000821115610cf1576040516350ddbb7560e11b8152600481018390526706f05b59d3b2000060248201526044016109ab565b818110610d1457604051632021db5560e21b8152600481018290526024016109ab565b5f610d1e84611964565b60038101549091506001600160801b03168314610da6576003810154604080516001600160801b039092168252602082018590526001600160a01b038616917f605baae98875f2133a40a8d489531e195b473e8cdb0c860a2d2b51d430eff588910160405180910390a26003810180546001600160801b0319166001600160801b0385161790555b6003810154600160801b90046001600160801b03168214610b2d57600381015460408051600160801b9092046001600160801b03168252602082018490526001600160a01b038616917f75c087963c80520a0d86e6333991c95d79462341f6960f2904396c23def23dd4910160405180910390a26003810180546001600160801b03808516600160801b02911617905550505050565b5f610e468261202e565b9392505050565b60ca8181548110610e5c575f80fd5b5f918252602090912001546001600160a01b0316905081565b610e966040518060600160405280603a8152602001613192603a9139611739565b61073b610ae56020830183612b01565b610eae611621565b610eb75f61210c565b565b60655433906001600160a01b03168114610f275760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016109ab565b61073b8161210c565b5f80610f3b8361202e565b91509150915091565b610f656040518060600160405280603881526020016131cc60389139611739565b805f5b81811015610b2d5736848483818110610f8357610f83612eb4565b6060029190910191505f9050610f9f6040830160208401612f3c565b6002811115610fb057610fb0612f28565b03610fdd57610fd8610fc56020830183612b01565b610fd28360400135611a43565b5f6117d0565b6110a6565b6001610fef6040830160208401612f3c565b600281111561100057611000612f28565b0361102957610fd86110156020830183612b01565b6110228360400135611a43565b60016117d0565b600261103b6040830160208401612f3c565b600281111561104c5761104c612f28565b0361106657610fd86110616020830183612b01565b612125565b6110766040820160208301612f3c565b600281111561108757611087612f28565b604051635374467d60e11b815260ff90911660048201526024016109ab565b50600101610f68565b5f80610f3b836122b9565b6110f86040518060400160405280601b81526020017f6578697450726f74656374696f6e4d6f64652861646472657373290000000000815250611739565b61073b81612125565b61113f6040518060400160405280601f81526020017f7570646174654d6178507269636528616464726573732c75696e743132382900815250611739565b610787828260016117d0565b61116c60405180606001604052806021815260200161320460219139611739565b6111758261193d565b611187816001600160401b031661200e565b5f61119183611964565b6001810154604080516001600160401b03600160501b9093048316815291851660208301529192506001600160a01b038516917f890c8f3ce148a0c50a68d44c085aa3228b8e72273a03dba4ecfd402f94033c1e910160405180910390a260010180546001600160401b03909216600160501b0267ffffffffffffffff60501b1990921691909117905550565b6112278161202e565b505050565b60608086858114158061123f5750808414155b1561125d57604051634ec4810560e11b815260040160405180910390fd5b806001600160401b0381111561127557611275612ea0565b60405190808252806020026020018201604052801561129e578160200160208202803683370190505b509250806001600160401b038111156112b9576112b9612ea0565b6040519080825280602002602001820160405280156112e2578160200160208202803683370190505b5091505f5b818110156113e5575f60c95f8c8c8581811061130557611305612eb4565b905060200201602081019061131a9190612b01565b6001600160a01b0316815260208101919091526040015f208054909150611370906001600160801b03168a8a8581811061135657611356612eb4565b905060200201602081019061136b9190612f5a565b6123f8565b85838151811061138257611382612eb4565b9115156020928302919091019091015280546113ba90600160801b90046001600160801b031688888581811061135657611356612eb4565b8483815181106113cc576113cc612eb4565b91151560209283029190910190910152506001016112e7565b5050965096945050505050565b5f54610100900460ff161580801561141057505f54600160ff909116105b806114295750303b15801561142957505f5460ff166001145b61148c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016109ab565b5f805460ff1916600117905580156114ad575f805461ff0019166101001790555b6114b6826124a1565b8015610787575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15050565b5f6115098261202e565b5092915050565b6001600160a01b0381165f90815260c960205260408120600181015460ff1680156115615750600181015461155d906001600160401b03600160501b820481169162010000900416612f73565b4210155b8015610e465750600381015481546001600160801b03600160801b928390048116926115949280831692919004166124d8565b109392505050565b5f610e46826122b9565b5f611509826122b9565b6115b8611621565b606580546001600160a01b0383166001600160a01b031990911681179091556115e96033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6033546001600160a01b03163314610eb75760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109ab565b6001600160a01b0381166116df5760405162461bcd60e51b815260206004820152602560248201527f696e76616c696420616365737320636f6e74726f6c206d616e61676572206164604482015264647265737360d81b60648201526084016109ab565b609780546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa091016114f3565b6097546040516318c5e8ab60e01b81525f916001600160a01b0316906318c5e8ab9061176b9033908690600401612fb4565b602060405180830381865afa158015611786573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117aa9190612fd7565b90508061078757333083604051634a3fa29360e01b81526004016109ab93929190612ff2565b6117d98361193d565b816001600160801b03165f036118025760405163a2b326dd60e01b815260040160405180910390fd5b5f61180c84611964565b90505f611818856119b1565b90505f83600181111561182d5761182d612f28565b036118af5781546001600160801b03600160801b9091048116908516118061185d575080846001600160801b0316115b1561189f5760405160016203590160e31b031981526001600160a01b03861660048201526001600160801b0385166024820152604481018290526064016109ab565b6118aa828686611a73565b611936565b60018360018111156118c3576118c3612f28565b036119365781546001600160801b0390811690851610806118ec575080846001600160801b0316105b1561192b57604051636df018fb60e11b81526001600160a01b03861660048201526001600160801b0385166024820152604481018290526064016109ab565b611936828686611ae3565b5050505050565b6001600160a01b03811661073b576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160a01b038082165f90815260c96020526040902060028101549091166119ac576040516349c9b68960e11b81526001600160a01b03831660048201526024016109ab565b919050565b6040516341976e0960e01b81526001600160a01b0382811660048301525f917f0000000000000000000000000000000000000000000000000000000000000000909116906341976e0990602401602060405180830381865afa158015611a19573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a3d919061301d565b92915050565b5f6001600160801b03821115611a6f5760405163339718d960e11b8152600481018390526024016109ab565b5090565b8254604080516001600160801b03928316815291831660208301526001600160a01b038416917f532579a79f45c3e02b749f0817d46bab6b256e8d1dae660d2f432d8032f21f2d910160405180910390a282546001600160801b0319166001600160801b03919091161790915550565b825460408051600160801b9092046001600160801b039081168352831660208301526001600160a01b038416917f067a69521c594f269500a8d1fd306d6cade49e09512a3fdf637a16aa1694dd88910160405180910390a282546001600160801b03918216600160801b0291161790915550565b611b608661193d565b611b72856001600160401b031661200e565b611b7b8461200e565b611b848361200e565b6001600160a01b038681165f90815260c960205260409020600201541615611bca57604051630d8cdd9d60e01b81526001600160a01b03871660048201526024016109ab565b66b1a2bc2ec50000841015611c0257604051630f4d736160e01b81526004810185905266b1a2bc2ec5000060248201526044016109ab565b6706f05b59d3b20000841115611c3c576040516350ddbb7560e11b8152600481018590526706f05b59d3b2000060248201526044016109ab565b838310611c5f57604051632021db5560e21b8152600481018490526024016109ab565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316866001600160a01b031603611cb15760405163c992a64b60e01b815260040160405180910390fd5b5f611cbe6109e8886119b1565b9050604051806101400160405280826001600160801b03168152602001826001600160801b031681526020015f1515815260200184151581526020015f6001600160401b03168152602001876001600160401b03168152602001886001600160a01b03168152602001866001600160801b03168152602001856001600160801b0316815260200183151581525060c95f896001600160a01b03166001600160a01b031681526020019081526020015f205f820151815f015f6101000a8154816001600160801b0302191690836001600160801b031602179055506020820151815f0160106101000a8154816001600160801b0302191690836001600160801b031602179055506040820151816001015f6101000a81548160ff02191690831515021790555060608201518160010160016101000a81548160ff02191690831515021790555060808201518160010160026101000a8154816001600160401b0302191690836001600160401b0316021790555060a082015181600101600a6101000a8154816001600160401b0302191690836001600160401b0316021790555060c0820151816002015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060e0820151816003015f6101000a8154816001600160801b0302191690836001600160801b031602179055506101008201518160030160106101000a8154816001600160801b0302191690836001600160801b03160217905550610120820151816004015f6101000a81548160ff02191690831515021790555090505060ca87908060018154018082558091505060019003905f5260205f20015f9091909190916101000a8154816001600160a01b0302191690836001600160a01b03160217905550866001600160a01b03167feaeb53cd979b528911d7ed793f11a25e44e5076bef70f458f60049acb4c322b682838989604051611fb894939291906001600160801b0394851681529290931660208301526001600160401b03166040820152606081019190915260800190565b60405180910390a2866001600160a01b03167fde52a1c70ed1ca343b25cca640873d949641bd6ec7a2c2d0e67374ce54ff89cd84604051611ffd911515815260200190565b60405180910390a250505050505050565b805f0361073b5760405163273e150360e21b815260040160405180910390fd5b5f805f61203a84612520565b90506120458161263e565b9093509150821580159061205857508115155b156120635750915091565b5f61206d826119b1565b6001600160a01b0383165f90815260c960205260409020600181015491925090610100900460ff166120af576120a48383846126c9565b509485945092505050565b5f805f6120bd848688612746565b9250925092505f6120d0858789856127c7565b90506120f08187866001600160801b0316866001600160801b03166128ce565b9099509750612100878a8a6126c9565b50505050505050915091565b606580546001600160a01b031916905561073b8161290b565b61212e8161193d565b5f61213882611964565b600181015490915060ff1661216b57604051638e001e1f60e01b81526001600160a01b03831660048201526024016109ab565b6001810154612192906001600160401b03600160501b820481169162010000900416612f73565b4210156121e7576001810154604051630874f3a160e01b81526001600160a01b03841660048201526001600160401b0362010000830481166024830152600160501b90920490911660448201526064016109ab565b80545f90612208906001600160801b0380821691600160801b9004166124d8565b6003830154909150600160801b90046001600160801b0316811061226c5760038201546040516311c9371560e01b81526001600160a01b038516600482015260248101839052600160801b9091046001600160801b031660448201526064016109ab565b60018201805469ffffffffffffffff00ff191690556040516001600160a01b038416907f6977cf3ab0aaccc6ceeb77ddaf93b2e1f1b9e6c7e39c7fb08314efbbea365741905f90a2505050565b5f805f6122c584612520565b90506122d08161263e565b909350915082158015906122e357508115155b156122ee5750915091565b5f6122f8826119b1565b6001600160a01b0383165f90815260c960205260409020600181015491925090610100900460ff1661232f57509485945092505050565b5f61233983611a43565b82549091505f906001600160801b0316841061235f5782546001600160801b0316612361565b815b83549091505f90600160801b90046001600160801b03168511612395578354600160801b90046001600160801b0316612397565b825b60018501549091505f9060ff16806123c7575060038501546123c7908790859085906001600160801b031661295c565b90506123e78187856001600160801b0316856001600160801b03166128ce565b909b909a5098505050505050505050565b5f6001600160801b038316158061241657506001600160801b038216155b1561242257505f611a3d565b5f826001600160801b0316846001600160801b031611612454576124468484613034565b6001600160801b0316612468565b61245e8385613034565b6001600160801b03165b905066b1a2bc2ec500006001600160801b03851661248e670de0b6b3a764000084613054565b612498919061306b565b11949350505050565b5f54610100900460ff166124c75760405162461bcd60e51b81526004016109ab9061308a565b6124cf6129db565b61073b81612a09565b5f806124f06001600160801b038086169085166130d5565b90506001600160801b03841661250e670de0b6b3a764000083613054565b612518919061306b565b949350505050565b5f61252a8261193d565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361257e575073bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb919050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316036125de57507f0000000000000000000000000000000000000000000000000000000000000000919050565b816001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561261a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a3d91906130e8565b6001600160a01b0381165f90815260c96020526040812060040154819060ff1661266c57505f928392509050565b6126967f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf700084612a2f565b91506126c27f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb897060084612a2f565b9050915091565b6001600160a01b0383165f90815260c9602052604090206004015460ff166126f057505050565b61271b7f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf70008484612a77565b6112277f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb89706008483612a77565b5f805f8061275386611a43565b87549091506001600160801b0380821691600160801b90048116905f90841683111561278b576127848a8986611a73565b5082915060015b816001600160801b0316846001600160801b031611156127b7576127b08a8986611ae3565b5082905060015b9199909850909650945050505050565b835460038501545f916127f39186916001600160801b0380821692600160801b9092048116911661295c565b156128b657600185015460ff1615808061280a5750825b156128355760018601805469ffffffffffffffff0000191662010000426001600160401b0316021790555b801561284c576001868101805460ff191690911790555b8554604080518781526001600160801b038084166020830152600160801b909304909216908201526001600160a01b038516907f0e25941e7a04e3bd937c28c403c9431e5fe5cf87df19322b5352bf457f8b1c5f9060600160405180910390a26001915050612518565b600185015460ff161561251857506001949350505050565b5f80856128df575083905080612902565b8385106128ec57836128ee565b845b8386116128fb57836128fd565b855b915091505b94509492505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f80670de0b6b3a76400006129718482612f73565b612984906001600160801b038816613054565b61298e919061306b565b90505f670de0b6b3a76400006129a485826130d5565b6129b7906001600160801b038816613054565b6129c1919061306b565b9050818711806129d057508087105b979650505050505050565b5f54610100900460ff16612a015760405162461bcd60e51b81526004016109ab9061308a565b610eb7612abe565b5f54610100900460ff166107325760405162461bcd60e51b81526004016109ab9061308a565b5f808383604051602001612a569291909182526001600160a01b0316602082015260400190565b60408051601f1981840301815291905280516020909101205c949350505050565b5f8383604051602001612a9d9291909182526001600160a01b0316602082015260400190565b60405160208183030381529060405280519060200120905081815d50505050565b5f54610100900460ff16612ae45760405162461bcd60e51b81526004016109ab9061308a565b610eb73361210c565b6001600160a01b038116811461073b575f80fd5b5f60208284031215612b11575f80fd5b8135610e4681612aed565b80356001600160801b03811681146119ac575f80fd5b5f8060408385031215612b43575f80fd5b8235612b4e81612aed565b9150612b5c60208401612b1c565b90509250929050565b602080825282518282018190525f9190848201906040850190845b81811015612ba55783516001600160a01b031683529284019291840191600101612b80565b50909695505050505050565b801515811461073b575f80fd5b5f8060408385031215612bcf575f80fd5b8235612bda81612aed565b91506020830135612bea81612bb1565b809150509250929050565b5f8060208385031215612c06575f80fd5b82356001600160401b0380821115612c1c575f80fd5b818501915085601f830112612c2f575f80fd5b813581811115612c3d575f80fd5b86602060c083028501011115612c51575f80fd5b60209290920196919550909350505050565b5f805f60608486031215612c75575f80fd5b8335612c8081612aed565b95602085013595506040909401359392505050565b5f60208284031215612ca5575f80fd5b5035919050565b5f60c08284031215612cbc575f80fd5b50919050565b5f8060208385031215612cd3575f80fd5b82356001600160401b0380821115612ce9575f80fd5b818501915085601f830112612cfc575f80fd5b813581811115612d0a575f80fd5b866020606083028501011115612c51575f80fd5b80356001600160401b03811681146119ac575f80fd5b5f8060408385031215612d45575f80fd5b8235612d5081612aed565b9150612b5c60208401612d1e565b5f8083601f840112612d6e575f80fd5b5081356001600160401b03811115612d84575f80fd5b6020830191508360208260051b8501011115612d9e575f80fd5b9250929050565b5f805f805f8060608789031215612dba575f80fd5b86356001600160401b0380821115612dd0575f80fd5b612ddc8a838b01612d5e565b90985096506020890135915080821115612df4575f80fd5b612e008a838b01612d5e565b90965094506040890135915080821115612e18575f80fd5b50612e2589828a01612d5e565b979a9699509497509295939492505050565b5f815180845260208085019450602084015f5b83811015612e68578151151587529582019590820190600101612e4a565b509495945050505050565b604081525f612e856040830185612e37565b8281036020840152612e978185612e37565b95945050505050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f60018201612eed57612eed612ec8565b5060010190565b5f60208284031215612f04575f80fd5b610e4682612d1e565b5f60208284031215612f1d575f80fd5b8135610e4681612bb1565b634e487b7160e01b5f52602160045260245ffd5b5f60208284031215612f4c575f80fd5b813560038110610e46575f80fd5b5f60208284031215612f6a575f80fd5b610e4682612b1c565b80820180821115611a3d57611a3d612ec8565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190525f9061251890830184612f86565b5f60208284031215612fe7575f80fd5b8151610e4681612bb1565b6001600160a01b038481168252831660208201526060604082018190525f90612e9790830184612f86565b5f6020828403121561302d575f80fd5b5051919050565b6001600160801b0382811682821603908082111561150957611509612ec8565b8082028115828204841417611a3d57611a3d612ec8565b5f8261308557634e487b7160e01b5f52601260045260245ffd5b500490565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b81810381811115611a3d57611a3d612ec8565b5f602082840312156130f8575f80fd5b8151610e4681612aed56fe7365744173736574426f756e64656450726963696e67456e61626c656428616464726573732c626f6f6c29736574546f6b656e436f6e666967732828616464726573732c75696e7436342c75696e743235362c75696e743235362c626f6f6c2c626f6f6c295b5d297365745468726573686f6c647328616464726573732c75696e743235362c75696e7432353629736574546f6b656e436f6e6669672828616464726573732c75696e7436342c75696e743235362c75696e743235362c626f6f6c2c626f6f6c292973796e635072696365426f756e6473416e6450726f74656374696f6e732828616464726573732c75696e74382c75696e74323536295b5d29736574436f6f6c646f776e506572696f6428616464726573732c75696e74363429a264697066735822122007b4cdcf907b0609fb9db3ddc0f71466578874b106706d462bbd01df9551438864736f6c63430008190033", + "devdoc": { + "author": "Venus", + "events": { + "Initialized(uint8)": { + "details": "Triggered when the contract has been initialized or reinitialized." + } + }, + "kind": "dev", + "methods": { + "acceptOwnership()": { + "details": "The new owner accepts the ownership transfer." + }, + "canExitProtection(address)": { + "details": "Returns true when both conditions are met: 1. Cooldown period has elapsed since last trigger 2. Price range has converged below exit threshold", + "params": { + "asset": "The underlying asset address" + }, + "returns": { + "_0": "True if protection can be disabled" + } + }, + "checkAndGetWindowDrift(address[],uint128[],uint128[])": { + "custom:error": "InvalidArrayLength if the input array lengths do not match", + "details": "Allows the keeper to identify stale windows in a single call, avoiding N individual reads. Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)", + "params": { + "assets": "Array of asset addresses to check", + "proposedMaxs": "Keeper's off-chain window maximum prices", + "proposedMins": "Keeper's off-chain window minimum prices" + }, + "returns": { + "needsMaxUpdate": "Whether maxPrice drift exceeds deadband for each asset", + "needsMinUpdate": "Whether minPrice drift exceeds deadband for each asset" + } + }, + "constructor": { + "custom:oz-upgrades-unsafe-allow": "constructor", + "params": { + "_resilientOracle": "Address of the ResilientOracle contract", + "nativeMarketAddress": "The address of a native market (for bsc it would be vBNB address)", + "vaiAddress": "The address of the VAI token, or address(0) if VAI is not deployed on the chain." + } + }, + "currentlyUsingProtectedPrice(address)": { + "params": { + "asset": "The underlying asset address" + }, + "returns": { + "_0": "True if the asset is currently using the protected price instead of spot" + } + }, + "exitProtectionMode(address)": { + "custom:access": "Only authorized monitor/keeper addresses", + "custom:error": "ProtectedPriceInactive if protection is not currently activeCooldownNotElapsed if cooldown period has not elapsedPriceRangeNotConverged if window range is still above exit threshold", + "custom:event": "ProtectionModeExited", + "details": "Called by the keeper/monitor after confirming price has normalised. Enforces two conditions on-chain: 1. Cooldown period has elapsed since the last trigger 2. Price range has converged below the exit threshold", + "params": { + "asset": "The underlying asset address" + } + }, + "getAllBoundedPricingEnabledAssets()": { + "details": "Iterates the append-only allAssets array and filters by isBoundedPricingEnabled. Gas-free for off-chain callers.", + "returns": { + "_0": "result Array of whitelisted asset addresses" + } + }, + "getBoundedCollateralPrice(address)": { + "custom:event": "MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold", + "details": "Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns the conservative (lower) price when protection is active. Used by keepers or direct callers who want atomic update + read.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "collateralPrice": "The bounded collateral price" + } + }, + "getBoundedCollateralPriceView(address)": { + "details": "Reads from transient cache first when the asset's `cachingEnabled` flag is `true` (populated by a prior updateProtectionState call in the same transaction). Falls back to ResilientOracle on cache miss or when caching is disabled. Returns min(spot, windowMin) when protection is active, spot otherwise.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "collateralPrice": "The bounded collateral price" + } + }, + "getBoundedDebtPrice(address)": { + "custom:event": "MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold", + "details": "Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns the conservative (higher) price when protection is active. Used by keepers or direct callers who want atomic update + read.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "debtPrice": "The bounded debt price" + } + }, + "getBoundedDebtPriceView(address)": { + "details": "Reads from transient cache first when the asset's `cachingEnabled` flag is `true` (populated by a prior updateProtectionState call in the same transaction). Falls back to ResilientOracle on cache miss or when caching is disabled. Returns max(spot, windowMax) when protection is active, spot otherwise.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "debtPrice": "The bounded debt price" + } + }, + "getBoundedPrices(address)": { + "custom:event": "MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold", + "details": "Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns both conservative prices in a single call.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "collateralPrice": "The bounded collateral price", + "debtPrice": "The bounded debt price" + } + }, + "getBoundedPricesView(address)": { + "details": "Reads from transient cache first when the asset's `cachingEnabled` flag is `true`; falls back to ResilientOracle on cache miss or when caching is disabled.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "collateralPrice": "The bounded collateral price", + "debtPrice": "The bounded debt price" + } + }, + "getInitializedAssets()": { + "returns": { + "_0": "Array of all initialized asset addresses" + } + }, + "initialize(address)": { + "params": { + "accessControlManager_": "Address of the access control manager contract" + } + }, + "isBoundedPricingEnabled(address)": { + "params": { + "asset": "The underlying asset address" + }, + "returns": { + "_0": "True if the asset is whitelisted" + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "pendingOwner()": { + "details": "Returns the address of the pending owner." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "setAccessControlManager(address)": { + "custom:access": "Only Governance", + "custom:event": "Emits NewAccessControlManager event", + "details": "Admin function to set address of AccessControlManager", + "params": { + "accessControlManager_": "The new address of the AccessControlManager" + } + }, + "setAssetBoundedPricingEnabled(address,bool)": { + "custom:access": "Only Governance", + "custom:error": "ProtectedPriceActive if trying to disable an asset while protection is active", + "custom:event": "BoundedPricingWhitelistUpdated", + "params": { + "asset": "The underlying asset address", + "enabled": "Whether bounded pricing should be enabled for the asset" + } + }, + "setCachingEnabled(address,bool)": { + "custom:access": "Only Governance", + "custom:error": "MarketNotInitialized if the asset has not been initialized", + "custom:event": "CachingEnabledUpdated", + "details": "When disabled, each view/non-view price call recomputes bounded prices from the live spot instead of reading or writing the transient slots. The initial value is set via the `enableCaching` argument of `setTokenConfig`.", + "params": { + "asset": "The underlying asset address", + "enabled": "Whether transient caching is enabled for this asset" + } + }, + "setCooldownPeriod(address,uint64)": { + "custom:access": "Only Governance", + "custom:event": "CooldownPeriodSet", + "params": { + "asset": "The underlying asset address", + "newCooldown": "The new cooldown period in seconds" + } + }, + "setThresholds(address,uint256,uint256)": { + "custom:access": "Only Governance", + "custom:error": "ThresholdBelowMinimum if newTriggerThreshold is below 5%ThresholdAboveMaximum if newTriggerThreshold is above 50%InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold", + "custom:event": "TriggerThresholdSet if the trigger threshold changedResetThresholdSet if the reset threshold changed", + "params": { + "asset": "The underlying asset address", + "newResetThreshold": "The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.", + "newTriggerThreshold": "The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold." + } + }, + "setTokenConfig((address,uint64,uint256,uint256,bool,bool))": { + "custom:access": "Only Governance", + "custom:event": "ProtectionInitializedBoundedPricingWhitelistUpdated", + "params": { + "tokenConfig_": "Token config input for the asset" + } + }, + "setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])": { + "custom:access": "Only Governance", + "custom:error": "InvalidArrayLength if the input array is empty", + "custom:event": "ProtectionInitialized for each assetBoundedPricingWhitelistUpdated for each asset", + "params": { + "tokenConfigs_": "Array of token config inputs, one per asset" + } + }, + "syncPriceBoundsAndProtections((address,uint8,uint256)[])": { + "custom:access": "Only authorized keeper addresses", + "custom:error": "InvalidKeeperAction if an item carries an unsupported action enum value", + "custom:event": "MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited", + "details": "Each item is processed in array order; any item revert rolls back the whole batch. `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode. Empty `actions` is a no-op success.", + "params": { + "actions": "The list of keeper actions to apply" + } + }, + "transferOwnership(address)": { + "details": "Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner." + }, + "updateMaxPrice(address,uint128)": { + "custom:access": "Only authorized keeper addresses", + "custom:event": "MaxPriceUpdated", + "details": "Called by the keeper to push corrected max values from the off-chain sliding window. Constraint: newMax must be at or above the current spot price.", + "params": { + "asset": "The underlying asset address", + "newMax": "The new maximum price" + } + }, + "updateMinPrice(address,uint128)": { + "custom:access": "Only authorized keeper addresses", + "custom:event": "MinPriceUpdated", + "details": "Called by the keeper to push corrected min values from the off-chain sliding window. Constraint: newMin must be at or below the current spot price.", + "params": { + "asset": "The underlying asset address", + "newMin": "The new minimum price" + } + }, + "updateProtectionState(address)": { + "custom:event": "MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold", + "details": "Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before liquidity calculations). Subsequent calls to getBoundedCollateralPriceView / getBoundedDebtPriceView within the same transaction will read from the transient cache instead of querying ResilientOracle again, keeping those functions as `view` and avoiding redundant oracle calls. The transient cache is only populated when the asset's `cachingEnabled` flag is `true`. When caching is disabled, view price reads fall through to live recomputation. Permissionless: anyone can call this, both for gas optimisation and to ensure every caller in the same transaction reads the correct, up-to-date bounded price.", + "params": { + "vToken": "vToken address" + } + } + }, + "stateVariables": { + "COLLATERAL_PRICE_CACHE_SLOT": { + "details": "custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/collateralCache\")) - 1)) & ~bytes32(uint256(0xff))" + }, + "DEBT_PRICE_CACHE_SLOT": { + "details": "custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\")) - 1)) & ~bytes32(uint256(0xff))" + }, + "RESILIENT_ORACLE": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + }, + "nativeMarket": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + }, + "vai": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + } + }, + "title": "DeviationBoundedOracle", + "version": 1 + }, + "userdoc": { + "errors": { + "CooldownNotElapsed(address,uint64,uint64)": [ + { + "notice": "Thrown when trying to disable protection before cooldown has elapsed" + } + ], + "InvalidArrayLength()": [ + { + "notice": "Thrown when the lengths of the arrays are not equal" + } + ], + "InvalidKeeperAction(uint8)": [ + { + "notice": "Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value" + } + ], + "InvalidMaxPrice(address,uint128,uint256)": [ + { + "notice": "Thrown when keeper tries to set maxPrice below current spot" + } + ], + "InvalidMinPrice(address,uint128,uint256)": [ + { + "notice": "Thrown when keeper tries to set minPrice above current spot" + } + ], + "InvalidResetThreshold(uint256)": [ + { + "notice": "Thrown when the exit threshold is set at or above the trigger threshold" + } + ], + "MarketAlreadyInitialized(address)": [ + { + "notice": "Thrown when trying to initialize an already initialized market" + } + ], + "MarketNotInitialized(address)": [ + { + "notice": "Thrown when trying to use or update protection for an asset that has not been initialized" + } + ], + "PriceExceedsUint128(uint256)": [ + { + "notice": "Thrown when a price exceeds uint128 max" + } + ], + "PriceRangeNotConverged(address,uint256,uint256)": [ + { + "notice": "Thrown when trying to disable protection before price range has converged" + } + ], + "ProtectedPriceActive(address)": [ + { + "notice": "Thrown when trying to disable bounded pricing for an asset while protection is active" + } + ], + "ProtectedPriceInactive(address)": [ + { + "notice": "Thrown when trying to disable protection that is not active" + } + ], + "ThresholdAboveMaximum(uint256,uint256)": [ + { + "notice": "Thrown when threshold is set above the maximum allowed value" + } + ], + "ThresholdBelowMinimum(uint256,uint256)": [ + { + "notice": "Thrown when threshold is set below the minimum allowed value" + } + ], + "Unauthorized(address,address,string)": [ + { + "notice": "Thrown when the action is prohibited by AccessControlManager" + } + ], + "VAINotAllowed()": [ + { + "notice": "Thrown when trying to initialize protection for VAI" + } + ], + "ZeroAddressNotAllowed()": [ + { + "notice": "Thrown if the supplied address is a zero address where it is not allowed" + } + ], + "ZeroPriceNotAllowed()": [ + { + "notice": "Thrown when a zero price is provided where a non-zero price is required" + } + ], + "ZeroValueNotAllowed()": [ + { + "notice": "Thrown if the supplied value is 0 where it is not allowed" + } + ] + }, + "events": { + "BoundedPricingWhitelistUpdated(address,bool)": { + "notice": "Emitted when an asset's whitelist status changes" + }, + "CachingEnabledUpdated(address,bool,bool)": { + "notice": "Emitted when the per-asset transient caching flag is toggled" + }, + "CooldownPeriodSet(address,uint64,uint64)": { + "notice": "Emitted when the cooldown period is updated for an asset" + }, + "MaxPriceUpdated(address,uint128,uint128)": { + "notice": "Emitted when the keeper updates the maximum price for an asset" + }, + "MinPriceUpdated(address,uint128,uint128)": { + "notice": "Emitted when the keeper updates the minimum price for an asset" + }, + "NewAccessControlManager(address,address)": { + "notice": "Emitted when access control manager contract address is changed" + }, + "ProtectionInitialized(address,uint128,uint128,uint64,uint256)": { + "notice": "Emitted when protection is initialized for an asset" + }, + "ProtectionModeExited(address)": { + "notice": "Emitted when protection mode is disabled for an asset" + }, + "ProtectionTriggered(address,uint256,uint128,uint128)": { + "notice": "Emitted when protection mode is triggered for an asset" + }, + "ResetThresholdSet(address,uint256,uint256)": { + "notice": "Emitted when the exit threshold is updated for an asset" + }, + "TriggerThresholdSet(address,uint256,uint256)": { + "notice": "Emitted when the entry threshold is updated for an asset" + } + }, + "kind": "user", + "methods": { + "COLLATERAL_PRICE_CACHE_SLOT()": { + "notice": "Transient storage slot for caching final collateral prices within a transaction" + }, + "DEBT_PRICE_CACHE_SLOT()": { + "notice": "Transient storage slot for caching final debt prices within a transaction" + }, + "KEEPER_DEADBAND()": { + "notice": "Keeper deadband threshold (5%) — min/max corrections below this are suppressed" + }, + "MAX_THRESHOLD()": { + "notice": "Maximum allowed threshold value (50%)" + }, + "MIN_THRESHOLD()": { + "notice": "Minimum allowed threshold value (5%) to account for keeper deadband" + }, + "NATIVE_TOKEN_ADDR()": { + "notice": "Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc) and can serve as any underlying asset of a market that supports native tokens" + }, + "RESILIENT_ORACLE()": { + "notice": "Resilient Oracle used to fetch spot prices" + }, + "accessControlManager()": { + "notice": "Returns the address of the access control manager contract" + }, + "allAssets(uint256)": { + "notice": "Append-only array of all assets ever initialized, used for enumeration" + }, + "assetProtectionConfig(address)": { + "notice": "Per-asset protection state" + }, + "canExitProtection(address)": { + "notice": "Checks if protection can be exited for an asset" + }, + "checkAndGetWindowDrift(address[],uint128[],uint128[])": { + "notice": "Batch-checks which assets' on-chain min/max have drifted beyond the deadband from the keeper's proposed window values" + }, + "constructor": { + "notice": "Constructor for the implementation contract. Sets immutable variables." + }, + "currentlyUsingProtectedPrice(address)": { + "notice": "Checks if the asset is currently using the protected (bounded) price" + }, + "exitProtectionMode(address)": { + "notice": "Exits protection mode for a given asset" + }, + "getAllBoundedPricingEnabledAssets()": { + "notice": "Returns all currently whitelisted asset addresses" + }, + "getBoundedCollateralPrice(address)": { + "notice": "Gets the bounded collateral price for a given vToken, updating protection state" + }, + "getBoundedCollateralPriceView(address)": { + "notice": "Gets the bounded collateral price for a given vToken (view variant)" + }, + "getBoundedDebtPrice(address)": { + "notice": "Gets the bounded debt price for a given vToken, updating protection state" + }, + "getBoundedDebtPriceView(address)": { + "notice": "Gets the bounded debt price for a given vToken (view variant)" + }, + "getBoundedPrices(address)": { + "notice": "Gets both the bounded collateral and debt prices for a given vToken, updating protection state" + }, + "getBoundedPricesView(address)": { + "notice": "Gets both the bounded collateral and debt prices for a given vToken (view variant)" + }, + "getInitializedAssets()": { + "notice": "Returns all asset addresses that have ever been initialized" + }, + "initialize(address)": { + "notice": "Initializes the contract admin" + }, + "isBoundedPricingEnabled(address)": { + "notice": "Checks if an asset is whitelisted for bounded pricing" + }, + "nativeMarket()": { + "notice": "Native market address" + }, + "setAccessControlManager(address)": { + "notice": "Sets the address of AccessControlManager" + }, + "setAssetBoundedPricingEnabled(address,bool)": { + "notice": "Sets whether an asset is enabled for bounded pricing" + }, + "setCachingEnabled(address,bool)": { + "notice": "Toggles transient caching of the bounded (collateral, debt) pair for an asset" + }, + "setCooldownPeriod(address,uint64)": { + "notice": "Sets the cooldown period for an asset" + }, + "setThresholds(address,uint256,uint256)": { + "notice": "Sets the trigger and reset thresholds for an asset" + }, + "setTokenConfig((address,uint64,uint256,uint256,bool,bool))": { + "notice": "Initializes protection for a new asset" + }, + "setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])": { + "notice": "Batch-initializes protection for multiple assets in a single transaction" + }, + "syncPriceBoundsAndProtections((address,uint8,uint256)[])": { + "notice": "Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check" + }, + "updateMaxPrice(address,uint128)": { + "notice": "Updates the maximum price in the rolling window for a given asset" + }, + "updateMinPrice(address,uint128)": { + "notice": "Updates the minimum price in the rolling window for a given asset" + }, + "updateProtectionState(address)": { + "notice": "Fetches the spot price, updates the protection window, and caches the resolved collateral and debt prices in transient storage for the duration of the transaction." + }, + "vai()": { + "notice": "VAI address" + } + }, + "notice": "The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations. It maintains a per-market rolling min/max price window. When the current spot price deviates significantly from the window bounds, protection mode activates automatically and conservative pricing kicks in: - Collateral is valued at min(spot, windowMin) — caps collateral value at recent window low - Debt is valued at max(spot, windowMax) — floors debt value at recent window high This protects against instantaneous or short-duration price manipulation attacks on low-liquidity collateral tokens. Sustained attacks beyond the window period are expected to be handled by off-chain monitoring systems. The oracle exposes both view and non-view price functions. The non-view variants update the price window and trigger protection. The view variants read stored state only. A transient price cache avoids redundant ResilientOracle calls within the same transaction when updateProtectionState is called before the view price reads.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 349, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8" + }, + { + "astId": 352, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 1019, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage" + }, + { + "astId": 221, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address" + }, + { + "astId": 341, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 114, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_pendingOwner", + "offset": 0, + "slot": "101", + "type": "t_address" + }, + { + "astId": 208, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 1940, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_accessControlManager", + "offset": 0, + "slot": "151", + "type": "t_contract(IAccessControlManagerV8)2125" + }, + { + "astId": 1945, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 2242, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "assetProtectionConfig", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_struct(MarketProtectionState)5622_storage)" + }, + { + "astId": 2246, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "allAssets", + "offset": 0, + "slot": "202", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 2251, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)48_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)48_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAccessControlManagerV8)2125": { + "encoding": "inplace", + "label": "contract IAccessControlManagerV8", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_struct(MarketProtectionState)5622_storage)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => struct IDeviationBoundedOracle.MarketProtectionState)", + "numberOfBytes": "32", + "value": "t_struct(MarketProtectionState)5622_storage" + }, + "t_struct(MarketProtectionState)5622_storage": { + "encoding": "inplace", + "label": "struct IDeviationBoundedOracle.MarketProtectionState", + "members": [ + { + "astId": 5594, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "minPrice", + "offset": 0, + "slot": "0", + "type": "t_uint128" + }, + { + "astId": 5597, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "maxPrice", + "offset": 16, + "slot": "0", + "type": "t_uint128" + }, + { + "astId": 5600, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "currentlyUsingProtectedPrice", + "offset": 0, + "slot": "1", + "type": "t_bool" + }, + { + "astId": 5603, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "isBoundedPricingEnabled", + "offset": 1, + "slot": "1", + "type": "t_bool" + }, + { + "astId": 5606, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "lastProtectionTriggeredAt", + "offset": 2, + "slot": "1", + "type": "t_uint64" + }, + { + "astId": 5609, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "cooldownPeriod", + "offset": 10, + "slot": "1", + "type": "t_uint64" + }, + { + "astId": 5612, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "asset", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 5615, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "triggerThreshold", + "offset": 0, + "slot": "3", + "type": "t_uint128" + }, + { + "astId": 5618, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "resetThreshold", + "offset": 16, + "slot": "3", + "type": "t_uint128" + }, + { + "astId": 5621, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "cachingEnabled", + "offset": 0, + "slot": "4", + "type": "t_bool" + } + ], + "numberOfBytes": "160" + }, + "t_uint128": { + "encoding": "inplace", + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "encoding": "inplace", + "label": "uint8", + "numberOfBytes": "1" + } + } + } +} diff --git a/deployments/bscmainnet/DeviationBoundedOracle_Proxy.json b/deployments/bscmainnet/DeviationBoundedOracle_Proxy.json new file mode 100644 index 00000000..f75fcf25 --- /dev/null +++ b/deployments/bscmainnet/DeviationBoundedOracle_Proxy.json @@ -0,0 +1,262 @@ +{ + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "receipt": { + "to": null, + "from": "0x9b0A3EAE7f174937d31745B710BbeA68e9D1BEf7", + "contractAddress": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "transactionIndex": 67, + "gasUsed": "589361", + "logsBloom": "0x0000000000000000000000004000000040000000000000020080000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000008000000000a000001000000000000000000000000000100000000020000000000000000000800000000800000000000010800000000400000000000000000000000000000000000000000000080000000000000800000000000000000000000000000000400000000000000800000000000000000000000000020000000000000000000040000000000000400000000000000000020000000000000000000000000008000000000000800000000000000000000000000", + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1", + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "logs": [ + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x00000000000000000000000016691f500541ca35bd63dd878b6d78728c9518ae" + ], + "data": "0x", + "logIndex": 214, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + }, + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000009b0a3eae7f174937d31745b710bbea68e9d1bef7" + ], + "data": "0x", + "logIndex": 215, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + }, + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": ["0x66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa0"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004788629abc6cfca10f9f969efdeaa1cf70c23555", + "logIndex": 216, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + }, + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 217, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + }, + { + "transactionIndex": 67, + "blockNumber": 95554965, + "transactionHash": "0xbf0472d7953289be0b662e908451615ffffe949eb44fa729b28a9ee0b28e46de", + "address": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bb765b741a5f3c2a338369dab539385534e3343", + "logIndex": 218, + "blockHash": "0x804d9e01a6b8d00ba375c22dc17c338ca833a462d0fad50b19745d692fa40ca1" + } + ], + "blockNumber": 95554965, + "cumulativeGasUsed": "8547839", + "status": 1, + "byzantium": true + }, + "args": [ + "0x16691f500541ca35bd63DD878B6D78728C9518AE", + "0x1BB765b741A5f3C2A338369DAb539385534E3343", + "0xc4d66de80000000000000000000000004788629abc6cfca10f9f969efdeaa1cf70c23555" + ], + "numDeployments": 1, + "solcInputHash": "b08018e2eab5f4ca5e3ba0c1798444d8", + "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"implementation_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"details\":\"This contract implements a proxy that is upgradeable by an admin. 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.\",\"events\":{\"AdminChanged(address,address)\":{\"details\":\"Emitted when the admin account has changed.\"},\"BeaconUpgraded(address)\":{\"details\":\"Emitted when the beacon is upgraded.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"admin()\":{\"details\":\"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`\"},\"constructor\":{\"details\":\"Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\"},\"implementation()\":{\"details\":\"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`\"},\"upgradeTo(address)\":{\"details\":\"Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\"},\"upgradeToAndCall(address,bytes)\":{\"details\":\"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}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol\":\"OptimizedTransparentUpgradeableProxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"hardhat-deploy/solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\\n * proxy whose upgrades are fully controlled by the current implementation.\\n */\\ninterface IERC1822Proxiable {\\n /**\\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\\n * address.\\n *\\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\\n * function revert if invoked through a proxy.\\n */\\n function proxiableUUID() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x93b4e21c931252739a1ec13ea31d3d35a5c068be3163ccab83e4d70c40355f03\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Proxy.sol\\\";\\nimport \\\"./ERC1967Upgrade.sol\\\";\\n\\n/**\\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\\n * implementation address that can be changed. This address is stored in storage in the location specified by\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\\n * implementation behind the proxy.\\n */\\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\\n /**\\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\\n *\\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\\n */\\n constructor(address _logic, bytes memory _data) payable {\\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.implementation\\\")) - 1));\\n _upgradeToAndCall(_logic, _data, false);\\n }\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _implementation() internal view virtual override returns (address impl) {\\n return ERC1967Upgrade._getImplementation();\\n }\\n}\\n\",\"keccak256\":\"0x6309f9f39dc6f4f45a24f296543867aa358e32946cd6b2874627a996d606b3a0\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../beacon/IBeacon.sol\\\";\\nimport \\\"../../interfaces/draft-IERC1822.sol\\\";\\nimport \\\"../../utils/Address.sol\\\";\\nimport \\\"../../utils/StorageSlot.sol\\\";\\n\\n/**\\n * @dev This abstract contract provides getters and event emitting update functions for\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\\n *\\n * _Available since v4.1._\\n *\\n * @custom:oz-upgrades-unsafe-allow delegatecall\\n */\\nabstract contract ERC1967Upgrade {\\n // This is the keccak-256 hash of \\\"eip1967.proxy.rollback\\\" subtracted by 1\\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\\n\\n /**\\n * @dev Storage slot with the address of the current implementation.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.implementation\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n\\n /**\\n * @dev Emitted when the implementation is upgraded.\\n */\\n event Upgraded(address indexed implementation);\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _getImplementation() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 implementation slot.\\n */\\n function _setImplementation(address newImplementation) private {\\n require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n }\\n\\n /**\\n * @dev Perform implementation upgrade\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeTo(address newImplementation) internal {\\n _setImplementation(newImplementation);\\n emit Upgraded(newImplementation);\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCall(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _upgradeTo(newImplementation);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(newImplementation, data);\\n }\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCallUUPS(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n // Upgrades from old implementations will perform a rollback test. This test requires the new\\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\\n // this special case will break upgrade paths from old UUPS implementation to new ones.\\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\\n _setImplementation(newImplementation);\\n } else {\\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\\n require(slot == _IMPLEMENTATION_SLOT, \\\"ERC1967Upgrade: unsupported proxiableUUID\\\");\\n } catch {\\n revert(\\\"ERC1967Upgrade: new implementation is not UUPS\\\");\\n }\\n _upgradeToAndCall(newImplementation, data, forceCall);\\n }\\n }\\n\\n /**\\n * @dev Storage slot with the admin of the contract.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.admin\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\\n\\n /**\\n * @dev Emitted when the admin account has changed.\\n */\\n event AdminChanged(address previousAdmin, address newAdmin);\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _getAdmin() internal view virtual returns (address) {\\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 admin slot.\\n */\\n function _setAdmin(address newAdmin) private {\\n require(newAdmin != address(0), \\\"ERC1967: new admin is the zero address\\\");\\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\\n }\\n\\n /**\\n * @dev Changes the admin of the proxy.\\n *\\n * Emits an {AdminChanged} event.\\n */\\n function _changeAdmin(address newAdmin) internal {\\n emit AdminChanged(_getAdmin(), newAdmin);\\n _setAdmin(newAdmin);\\n }\\n\\n /**\\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\\n */\\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\\n\\n /**\\n * @dev Emitted when the beacon is upgraded.\\n */\\n event BeaconUpgraded(address indexed beacon);\\n\\n /**\\n * @dev Returns the current beacon.\\n */\\n function _getBeacon() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new beacon in the EIP1967 beacon slot.\\n */\\n function _setBeacon(address newBeacon) private {\\n require(Address.isContract(newBeacon), \\\"ERC1967: new beacon is not a contract\\\");\\n require(Address.isContract(IBeacon(newBeacon).implementation()), \\\"ERC1967: beacon implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\\n }\\n\\n /**\\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\\n *\\n * Emits a {BeaconUpgraded} event.\\n */\\n function _upgradeBeaconToAndCall(\\n address newBeacon,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _setBeacon(newBeacon);\\n emit BeaconUpgraded(newBeacon);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x17668652127feebed0ce8d9431ef95ccc8c4292f03e3b8cf06c6ca16af396633\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\\n * be specified by overriding the virtual {_implementation} function.\\n *\\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\\n * different contract through the {_delegate} function.\\n *\\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\\n */\\nabstract contract Proxy {\\n /**\\n * @dev Delegates the current call to `implementation`.\\n *\\n * This function does not return to its internal call site, it will return directly to the external caller.\\n */\\n function _delegate(address implementation) internal virtual {\\n assembly {\\n // Copy msg.data. We take full control of memory in this inline assembly\\n // block because it will not return to Solidity code. We overwrite the\\n // Solidity scratch pad at memory position 0.\\n calldatacopy(0, 0, calldatasize())\\n\\n // Call the implementation.\\n // out and outsize are 0 because we don't know the size yet.\\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\\n\\n // Copy the returned data.\\n returndatacopy(0, 0, returndatasize())\\n\\n switch result\\n // delegatecall returns 0 on error.\\n case 0 {\\n revert(0, returndatasize())\\n }\\n default {\\n return(0, returndatasize())\\n }\\n }\\n }\\n\\n /**\\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\\n * and {_fallback} should delegate.\\n */\\n function _implementation() internal view virtual returns (address);\\n\\n /**\\n * @dev Delegates the current call to the address returned by `_implementation()`.\\n *\\n * This function does not return to its internall call site, it will return directly to the external caller.\\n */\\n function _fallback() internal virtual {\\n _beforeFallback();\\n _delegate(_implementation());\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\\n * function in the contract matches the call data.\\n */\\n fallback() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\\n * is empty.\\n */\\n receive() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\\n * call, or as part of the Solidity `fallback` or `receive` functions.\\n *\\n * If overriden should call `super._beforeFallback()`.\\n */\\n function _beforeFallback() internal virtual {}\\n}\\n\",\"keccak256\":\"0xd5d1fd16e9faff7fcb3a52e02a8d49156f42a38a03f07b5f1810c21c2149a8ab\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\\n */\\ninterface IBeacon {\\n /**\\n * @dev Must return an address that can be used as a delegate call target.\\n *\\n * {BeaconProxy} will check that this address is a contract.\\n */\\n function implementation() external view returns (address);\\n}\\n\",\"keccak256\":\"0xd50a3421ac379ccb1be435fa646d66a65c986b4924f0849839f08692f39dde61\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3777e696b62134e6177440dbe6e6601c0c156a443f57167194b67e75527439de\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/utils/StorageSlot.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for reading and writing primitive types to specific storage slots.\\n *\\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\\n * This library helps with reading and writing to such slots without the need for inline assembly.\\n *\\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\\n *\\n * Example usage to set ERC1967 implementation slot:\\n * ```\\n * contract ERC1967 {\\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n *\\n * function _getImplementation() internal view returns (address) {\\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n * }\\n *\\n * function _setImplementation(address newImplementation) internal {\\n * require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n * }\\n * }\\n * ```\\n *\\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\\n */\\nlibrary StorageSlot {\\n struct AddressSlot {\\n address value;\\n }\\n\\n struct BooleanSlot {\\n bool value;\\n }\\n\\n struct Bytes32Slot {\\n bytes32 value;\\n }\\n\\n struct Uint256Slot {\\n uint256 value;\\n }\\n\\n /**\\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\\n */\\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\\n */\\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\\n */\\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\\n */\\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfe1b7a9aa2a530a9e705b220e26cd584e2fbdc9602a3a1066032b12816b46aca\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\\\";\\n\\n/**\\n * @dev This contract implements a proxy that is upgradeable by an admin.\\n *\\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\\n * clashing], which can potentially be used in an attack, this contract uses the\\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\\n * things that go hand in hand:\\n *\\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\\n * that call matches one of the admin functions exposed by the proxy itself.\\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\\n * \\\"admin cannot fallback to proxy target\\\".\\n *\\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\\n * to sudden errors when trying to call a function from the proxy implementation.\\n *\\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\\n */\\ncontract OptimizedTransparentUpgradeableProxy is ERC1967Proxy {\\n address internal immutable _ADMIN;\\n\\n /**\\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\\n */\\n constructor(\\n address _logic,\\n address admin_,\\n bytes memory _data\\n ) payable ERC1967Proxy(_logic, _data) {\\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.admin\\\")) - 1));\\n _ADMIN = admin_;\\n\\n // still store it to work with EIP-1967\\n bytes32 slot = _ADMIN_SLOT;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(slot, admin_)\\n }\\n emit AdminChanged(address(0), admin_);\\n }\\n\\n /**\\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\\n */\\n modifier ifAdmin() {\\n if (msg.sender == _getAdmin()) {\\n _;\\n } else {\\n _fallback();\\n }\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\\n */\\n function admin() external ifAdmin returns (address admin_) {\\n admin_ = _getAdmin();\\n }\\n\\n /**\\n * @dev Returns the current implementation.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\\n */\\n function implementation() external ifAdmin returns (address implementation_) {\\n implementation_ = _implementation();\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\\n */\\n function upgradeTo(address newImplementation) external ifAdmin {\\n _upgradeToAndCall(newImplementation, bytes(\\\"\\\"), false);\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\\n * proxied contract.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\\n */\\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\\n _upgradeToAndCall(newImplementation, data, true);\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _admin() internal view virtual returns (address) {\\n return _getAdmin();\\n }\\n\\n /**\\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\\n */\\n function _beforeFallback() internal virtual override {\\n require(msg.sender != _getAdmin(), \\\"TransparentUpgradeableProxy: admin cannot fallback to proxy target\\\");\\n super._beforeFallback();\\n }\\n\\n function _getAdmin() internal view virtual override returns (address) {\\n return _ADMIN;\\n }\\n}\\n\",\"keccak256\":\"0xa30117644e27fa5b49e162aae2f62b36c1aca02f801b8c594d46e2024963a534\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60a0604052604051610c8f380380610c8f8339810160408190526100229161039f565b828161004f60017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd61046a565b5f80516020610c488339815191521461006a5761006a610489565b61007582825f610123565b506100a3905060017fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610461046a565b5f80516020610c28833981519152146100be576100be610489565b6001600160a01b03821660808190525f80516020610c28833981519152838155604080515f8152602081019390935290917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050506104e8565b61012c8361014e565b5f825111806101385750805b1561014957610147838361018d565b505b505050565b610157816101bb565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606101b28383604051806060016040528060278152602001610c686027913961025b565b90505b92915050565b6001600160a01b0381163b61022d5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b5f80516020610c4883398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6102c35760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610224565b5f80856001600160a01b0316856040516102dd919061049d565b5f60405180830381855af49150503d805f8114610315576040519150601f19603f3d011682016040523d82523d5f602084013e61031a565b606091505b50909250905061032b828286610337565b925050505b9392505050565b60608315610346575081610330565b8251156103565782518084602001fd5b8160405162461bcd60e51b815260040161022491906104b3565b80516001600160a01b0381168114610386575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f805f606084860312156103b1575f80fd5b6103ba84610370565b92506103c860208501610370565b60408501519092506001600160401b03808211156103e4575f80fd5b818601915086601f8301126103f7575f80fd5b8151818111156104095761040961038b565b604051601f8201601f19908116603f011681019083821181831017156104315761043161038b565b81604052828152896020848701011115610449575f80fd5b8260208601602083015e5f6020848301015280955050505050509250925092565b818103818111156101b557634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52600160045260245ffd5b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b6080516107066105225f395f818160eb0152818161013f015281816101bf0152818161020801528181610239015261025d01526107065ff3fe608060405260043610610042575f3560e01c80633659cfe6146100595780634f1ef286146100785780635c60da1b1461008b578063f851a440146100bb57610051565b366100515761004f6100cf565b005b61004f6100cf565b348015610064575f80fd5b5061004f6100733660046105c9565b6100e9565b61004f6100863660046105e2565b61013d565b348015610096575f80fd5b5061009f6101bc565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c6575f80fd5b5061009f610205565b6100d761025b565b6100e76100e2610309565b61033b565b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610135576101328160405180602001604052805f8152505f610359565b50565b6101326100cf565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101b4576101af8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610359915050565b505050565b6101af6100cf565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa576101f5610309565b905090565b6102026100cf565b90565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa57507f000000000000000000000000000000000000000000000000000000000000000090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036100e75760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101f57f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b365f80375f80365f845af43d5f803e808015610355573d5ff35b3d5ffd5b61036283610383565b5f8251118061036e5750805b156101af5761037d83836103c2565b50505050565b61038c816103ee565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103e783836040518060600160405280602781526020016106aa6027913961049c565b9392505050565b6001600160a01b0381163b61045b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610300565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6105045760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610300565b5f80856001600160a01b03168560405161051e919061065e565b5f60405180830381855af49150503d805f8114610556576040519150601f19603f3d011682016040523d82523d5f602084013e61055b565b606091505b509150915061056b828286610575565b9695505050505050565b606083156105845750816103e7565b8251156105945782518084602001fd5b8160405162461bcd60e51b81526004016103009190610674565b80356001600160a01b03811681146105c4575f80fd5b919050565b5f602082840312156105d9575f80fd5b6103e7826105ae565b5f805f604084860312156105f4575f80fd5b6105fd846105ae565b9250602084013567ffffffffffffffff80821115610619575f80fd5b818601915086601f83011261062c575f80fd5b81358181111561063a575f80fd5b87602082850101111561064b575f80fd5b6020830194508093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d447ec2a4b47962d9e959ca0ec6571d77747999259501e773b66fdcaef0e50bf64736f6c63430008190033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", + "deployedBytecode": "0x608060405260043610610042575f3560e01c80633659cfe6146100595780634f1ef286146100785780635c60da1b1461008b578063f851a440146100bb57610051565b366100515761004f6100cf565b005b61004f6100cf565b348015610064575f80fd5b5061004f6100733660046105c9565b6100e9565b61004f6100863660046105e2565b61013d565b348015610096575f80fd5b5061009f6101bc565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c6575f80fd5b5061009f610205565b6100d761025b565b6100e76100e2610309565b61033b565b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610135576101328160405180602001604052805f8152505f610359565b50565b6101326100cf565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101b4576101af8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610359915050565b505050565b6101af6100cf565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa576101f5610309565b905090565b6102026100cf565b90565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa57507f000000000000000000000000000000000000000000000000000000000000000090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036100e75760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101f57f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b365f80375f80365f845af43d5f803e808015610355573d5ff35b3d5ffd5b61036283610383565b5f8251118061036e5750805b156101af5761037d83836103c2565b50505050565b61038c816103ee565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103e783836040518060600160405280602781526020016106aa6027913961049c565b9392505050565b6001600160a01b0381163b61045b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610300565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6105045760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610300565b5f80856001600160a01b03168560405161051e919061065e565b5f60405180830381855af49150503d805f8114610556576040519150601f19603f3d011682016040523d82523d5f602084013e61055b565b606091505b509150915061056b828286610575565b9695505050505050565b606083156105845750816103e7565b8251156105945782518084602001fd5b8160405162461bcd60e51b81526004016103009190610674565b80356001600160a01b03811681146105c4575f80fd5b919050565b5f602082840312156105d9575f80fd5b6103e7826105ae565b5f805f604084860312156105f4575f80fd5b6105fd846105ae565b9250602084013567ffffffffffffffff80821115610619575f80fd5b818601915086601f83011261062c575f80fd5b81358181111561063a575f80fd5b87602082850101111561064b575f80fd5b6020830194508093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d447ec2a4b47962d9e959ca0ec6571d77747999259501e773b66fdcaef0e50bf64736f6c63430008190033", + "devdoc": { + "details": "This contract implements a proxy that is upgradeable by an admin. 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.", + "events": { + "AdminChanged(address,address)": { + "details": "Emitted when the admin account has changed." + }, + "BeaconUpgraded(address)": { + "details": "Emitted when the beacon is upgraded." + }, + "Upgraded(address)": { + "details": "Emitted when the implementation is upgraded." + } + }, + "kind": "dev", + "methods": { + "admin()": { + "details": "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`" + }, + "constructor": { + "details": "Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}." + }, + "implementation()": { + "details": "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`" + }, + "upgradeTo(address)": { + "details": "Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}." + }, + "upgradeToAndCall(address,bytes)": { + "details": "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}." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} diff --git a/deployments/bscmainnet/solcInputs/b08018e2eab5f4ca5e3ba0c1798444d8.json b/deployments/bscmainnet/solcInputs/b08018e2eab5f4ca5e3ba0c1798444d8.json new file mode 100644 index 00000000..0355b9cb --- /dev/null +++ b/deployments/bscmainnet/solcInputs/b08018e2eab5f4ca5e3ba0c1798444d8.json @@ -0,0 +1,349 @@ +{ + "language": "Solidity", + "sources": { + "@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorInterface {\n function latestAnswer() external view returns (int256);\n\n function latestTimestamp() external view returns (uint256);\n\n function latestRound() external view returns (uint256);\n\n function getAnswer(uint256 roundId) external view returns (int256);\n\n function getTimestamp(uint256 roundId) external view returns (uint256);\n\n event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);\n\n event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);\n}\n" + }, + "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./AggregatorInterface.sol\";\nimport \"./AggregatorV3Interface.sol\";\n\ninterface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface {}\n" + }, + "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n function decimals() external view returns (uint8);\n\n function description() external view returns (string memory);\n\n function version() external view returns (uint256);\n\n function getRoundData(uint80 _roundId)\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./OwnableUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n function __Ownable2Step_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable2Step_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() public virtual {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n function __Ownable_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable_init_unchained() internal onlyInitializing {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../../utils/AddressUpgradeable.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n * @custom:oz-retyped-from bool\n */\n uint8 private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint8 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts.\n *\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\n * constructor.\n *\n * Emits an {Initialized} event.\n */\n modifier initializer() {\n bool isTopLevelCall = !_initializing;\n require(\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\n \"Initializable: contract is already initialized\"\n );\n _initialized = 1;\n if (isTopLevelCall) {\n _initializing = true;\n }\n _;\n if (isTopLevelCall) {\n _initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\n * are added through upgrades and that require initialization.\n *\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\n * cannot be nested. If one is invoked in the context of another, execution will revert.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n *\n * WARNING: setting the version to 255 will prevent any future reinitialization.\n *\n * Emits an {Initialized} event.\n */\n modifier reinitializer(uint8 version) {\n require(!_initializing && _initialized < version, \"Initializable: contract is already initialized\");\n _initialized = version;\n _initializing = true;\n _;\n _initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n *\n * Emits an {Initialized} event the first time it is successfully executed.\n */\n function _disableInitializers() internal virtual {\n require(!_initializing, \"Initializable: contract is initializing\");\n if (_initialized != type(uint8).max) {\n _initialized = type(uint8).max;\n emit Initialized(type(uint8).max);\n }\n }\n\n /**\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\n */\n function _getInitializedVersion() internal view returns (uint8) {\n return _initialized;\n }\n\n /**\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\n */\n function _isInitializing() internal view returns (bool) {\n return _initializing;\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract PausableUpgradeable is Initializable, ContextUpgradeable {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n function __Pausable_init() internal onlyInitializing {\n __Pausable_init_unchained();\n }\n\n function __Pausable_init_unchained() internal onlyInitializing {\n _paused = false;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n _requireNotPaused();\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n _requirePaused();\n _;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Throws if the contract is paused.\n */\n function _requireNotPaused() internal view virtual {\n require(!paused(), \"Pausable: paused\");\n }\n\n /**\n * @dev Throws if the contract is not paused.\n */\n function _requirePaused() internal view virtual {\n require(paused(), \"Pausable: not paused\");\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary AddressUpgradeable {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n *\n * Furthermore, `isContract` will also return true if the target contract within\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\n * which only has an effect at the end of a transaction.\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"./extensions/IERC20Metadata.sol\";\nimport \"../../utils/Context.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(address from, address to, uint256 amount) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(address owner, address spender, uint256 amount) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\n\nimport \"./IAccessControlManagerV8.sol\";\n\n/**\n * @title AccessControlledV8\n * @author Venus\n * @notice This contract is helper between access control manager and actual contract. This contract further inherited by other contract (using solidity 0.8.13)\n * to integrate access controlled mechanism. It provides initialise methods and verifying access methods.\n */\nabstract contract AccessControlledV8 is Initializable, Ownable2StepUpgradeable {\n /// @notice Access control manager contract\n IAccessControlManagerV8 internal _accessControlManager;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n\n /// @notice Emitted when access control manager contract address is changed\n event NewAccessControlManager(address oldAccessControlManager, address newAccessControlManager);\n\n /// @notice Thrown when the action is prohibited by AccessControlManager\n error Unauthorized(address sender, address calledContract, string methodSignature);\n\n function __AccessControlled_init(address accessControlManager_) internal onlyInitializing {\n __Ownable2Step_init();\n __AccessControlled_init_unchained(accessControlManager_);\n }\n\n function __AccessControlled_init_unchained(address accessControlManager_) internal onlyInitializing {\n _setAccessControlManager(accessControlManager_);\n }\n\n /**\n * @notice Sets the address of AccessControlManager\n * @dev Admin function to set address of AccessControlManager\n * @param accessControlManager_ The new address of the AccessControlManager\n * @custom:event Emits NewAccessControlManager event\n * @custom:access Only Governance\n */\n function setAccessControlManager(address accessControlManager_) external onlyOwner {\n _setAccessControlManager(accessControlManager_);\n }\n\n /**\n * @notice Returns the address of the access control manager contract\n */\n function accessControlManager() external view returns (IAccessControlManagerV8) {\n return _accessControlManager;\n }\n\n /**\n * @dev Internal function to set address of AccessControlManager\n * @param accessControlManager_ The new address of the AccessControlManager\n */\n function _setAccessControlManager(address accessControlManager_) internal {\n require(address(accessControlManager_) != address(0), \"invalid acess control manager address\");\n address oldAccessControlManager = address(_accessControlManager);\n _accessControlManager = IAccessControlManagerV8(accessControlManager_);\n emit NewAccessControlManager(oldAccessControlManager, accessControlManager_);\n }\n\n /**\n * @notice Reverts if the call is not allowed by AccessControlManager\n * @param signature Method signature\n */\n function _checkAccessAllowed(string memory signature) internal view {\n bool isAllowedToCall = _accessControlManager.isAllowedToCall(msg.sender, signature);\n\n if (!isAllowedToCall) {\n revert Unauthorized(msg.sender, address(this), signature);\n }\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/access/IAccessControl.sol\";\n\n/**\n * @title IAccessControlManagerV8\n * @author Venus\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\n */\ninterface IAccessControlManagerV8 is IAccessControl {\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\n\n function revokeCallPermission(\n address contractAddress,\n string calldata functionSig,\n address accountToRevoke\n ) external;\n\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\n\n function hasPermission(\n address account,\n address contractAddress,\n string calldata functionSig\n ) external view returns (bool);\n}\n" + }, + "@venusprotocol/solidity-utilities/contracts/constants.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\nuint256 constant EXP_SCALE = 1e18;\n\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\nuint256 constant MANTISSA_ONE = EXP_SCALE;\n\n/// @dev The approximate number of seconds per year\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\n" + }, + "@venusprotocol/solidity-utilities/contracts/validators.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\nerror ZeroAddressNotAllowed();\n\n/// @notice Thrown if the supplied value is 0 where it is not allowed\nerror ZeroValueNotAllowed();\n\n/// @notice Checks if the provided address is nonzero, reverts otherwise\n/// @param address_ Address to check\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\nfunction ensureNonzeroAddress(address address_) pure {\n if (address_ == address(0)) {\n revert ZeroAddressNotAllowed();\n }\n}\n\n/// @notice Checks if the provided value is nonzero, reverts otherwise\n/// @param value_ Value to check\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\nfunction ensureNonzeroValue(uint256 value_) pure {\n if (value_ == 0) {\n revert ZeroValueNotAllowed();\n }\n}\n" + }, + "contracts/DeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { VBep20Interface } from \"./interfaces/VBep20Interface.sol\";\nimport { ResilientOracleInterface } from \"./interfaces/OracleInterface.sol\";\nimport { IDeviationBoundedOracle } from \"./interfaces/IDeviationBoundedOracle.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { Transient } from \"./lib/Transient.sol\";\n\n/**\n * @title DeviationBoundedOracle\n * @author Venus\n * @notice The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations.\n *\n * It maintains a per-market rolling min/max price window. When the current spot price deviates\n * significantly from the window bounds, protection mode activates automatically and conservative\n * pricing kicks in:\n * - Collateral is valued at min(spot, windowMin) — caps collateral value at recent window low\n * - Debt is valued at max(spot, windowMax) — floors debt value at recent window high\n *\n * This protects against instantaneous or short-duration price manipulation attacks on low-liquidity\n * collateral tokens. Sustained attacks beyond the window period are expected to be handled by\n * off-chain monitoring systems.\n *\n * The oracle exposes both view and non-view price functions. The non-view variants update the\n * price window and trigger protection. The view variants read stored state only. A transient\n * price cache avoids redundant ResilientOracle calls within the same transaction when\n * updateProtectionState is called before the view price reads.\n */\ncontract DeviationBoundedOracle is AccessControlledV8, IDeviationBoundedOracle {\n /// @notice Minimum allowed threshold value (5%) to account for keeper deadband\n uint256 public constant MIN_THRESHOLD = 5e16;\n\n /// @notice Maximum allowed threshold value (50%)\n uint256 public constant MAX_THRESHOLD = 50e16;\n\n /// @notice Keeper deadband threshold (5%) — min/max corrections below this are suppressed\n uint256 public constant KEEPER_DEADBAND = 5e16;\n\n /// @notice Resilient Oracle used to fetch spot prices\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Native market address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable nativeMarket;\n\n /// @notice VAI address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vai;\n\n /// @notice Transient storage slot for caching final collateral prices within a transaction\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/collateralCache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant COLLATERAL_PRICE_CACHE_SLOT =\n 0x7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf7000;\n\n /// @notice Transient storage slot for caching final debt prices within a transaction\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant DEBT_PRICE_CACHE_SLOT = 0x84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb8970600;\n\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\n /// and can serve as any underlying asset of a market that supports native tokens\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Per-asset protection state\n mapping(address => MarketProtectionState) public assetProtectionConfig;\n\n /// @notice Append-only array of all assets ever initialized, used for enumeration\n address[] public allAssets;\n\n /// @notice Storage gap for upgrades\n uint256[48] private __gap;\n\n /**\n * @notice Constructor for the implementation contract. Sets immutable variables.\n * @param _resilientOracle Address of the ResilientOracle contract\n * @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\n * @param vaiAddress The address of the VAI token, or address(0) if VAI is not deployed on the chain.\n * @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(ResilientOracleInterface _resilientOracle, address nativeMarketAddress, address vaiAddress) {\n ensureNonzeroAddress(address(_resilientOracle));\n ensureNonzeroAddress(nativeMarketAddress);\n RESILIENT_ORACLE = _resilientOracle;\n nativeMarket = nativeMarketAddress;\n vai = vaiAddress;\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n // ----- Non-view price functions (update window + trigger protection) -----\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns the conservative (lower) price when protection is active.\n * Used by keepers or direct callers who want atomic update + read.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice) {\n (collateralPrice, ) = _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns the conservative (higher) price when protection is active.\n * Used by keepers or direct callers who want atomic update + read.\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice) {\n (, debtPrice) = _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns both conservative prices in a single call.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice) {\n return _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Fetches the spot price, updates the protection window, and caches the resolved\n * collateral and debt prices in transient storage for the duration of the transaction.\n * @dev Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before\n * liquidity calculations). Subsequent calls to getBoundedCollateralPriceView /\n * getBoundedDebtPriceView within the same transaction will read from the transient cache\n * instead of querying ResilientOracle again, keeping those functions as `view` and\n * avoiding redundant oracle calls.\n * The transient cache is only populated when the asset's `cachingEnabled` flag is `true`.\n * When caching is disabled, view price reads fall through to live recomputation.\n * Permissionless: anyone can call this, both for gas optimisation and to ensure every\n * caller in the same transaction reads the correct, up-to-date bounded price.\n * @param vToken vToken address\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external {\n _updateAndGetBoundedPrices(vToken);\n }\n\n // ----- View price functions (read stored/cached state only) -----\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`\n * (populated by a prior updateProtectionState call in the same transaction). Falls back\n * to ResilientOracle on cache miss or when caching is disabled.\n * Returns min(spot, windowMin) when protection is active, spot otherwise.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 collateralPrice) {\n (collateralPrice, ) = _computeBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`\n * (populated by a prior updateProtectionState call in the same transaction). Falls back\n * to ResilientOracle on cache miss or when caching is disabled.\n * Returns max(spot, windowMax) when protection is active, spot otherwise.\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 debtPrice) {\n (, debtPrice) = _computeBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice) {\n return _computeBoundedPrices(vToken);\n }\n\n // ----- Keeper functions -----\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @dev Called by the keeper to push corrected min values from the off-chain sliding window.\n * Constraint: newMin must be at or below the current spot price.\n * @param asset The underlying asset address\n * @param newMin The new minimum price\n * @custom:access Only authorized keeper addresses\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external {\n _checkAccessAllowed(\"updateMinPrice(address,uint128)\");\n _validateAndUpdateBound(asset, newMin, PriceBoundType.MIN);\n }\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @dev Called by the keeper to push corrected max values from the off-chain sliding window.\n * Constraint: newMax must be at or above the current spot price.\n * @param asset The underlying asset address\n * @param newMax The new maximum price\n * @custom:access Only authorized keeper addresses\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external {\n _checkAccessAllowed(\"updateMaxPrice(address,uint128)\");\n _validateAndUpdateBound(asset, newMax, PriceBoundType.MAX);\n }\n\n /**\n * @notice Exits protection mode for a given asset\n * @dev Called by the keeper/monitor after confirming price has normalised.\n * Enforces two conditions on-chain:\n * 1. Cooldown period has elapsed since the last trigger\n * 2. Price range has converged below the exit threshold\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\n * @custom:error PriceRangeNotConverged if window range is still above exit threshold\n * @custom:event ProtectionModeExited\n */\n function exitProtectionMode(address asset) external {\n _checkAccessAllowed(\"exitProtectionMode(address)\");\n _exitProtectionMode(asset);\n }\n\n /**\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\n * Empty `actions` is a no-op success.\n * @param actions The list of keeper actions to apply\n * @custom:access Only authorized keeper addresses\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\n */\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external {\n _checkAccessAllowed(\"syncPriceBoundsAndProtections((address,uint8,uint256)[])\");\n uint256 len = actions.length;\n for (uint256 i; i < len; ++i) {\n KeeperActionItem calldata item = actions[i];\n if (item.action == KeeperAction.SetMinPrice) {\n _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MIN);\n } else if (item.action == KeeperAction.SetMaxPrice) {\n _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MAX);\n } else if (item.action == KeeperAction.ExitProtectionMode) {\n _exitProtectionMode(item.asset);\n } else {\n revert InvalidKeeperAction(uint8(item.action));\n }\n }\n }\n\n // ----- Admin functions (governance-gated) -----\n\n /**\n * @notice Initializes protection for a new asset\n * @param tokenConfig_ Token config input for the asset\n * @custom:access Only Governance\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external {\n _checkAccessAllowed(\"setTokenConfig((address,uint64,uint256,uint256,bool,bool))\");\n _setTokenConfig(\n tokenConfig_.asset,\n tokenConfig_.cooldownPeriod,\n tokenConfig_.triggerThreshold,\n tokenConfig_.resetThreshold,\n tokenConfig_.enableBoundedPricing,\n tokenConfig_.enableCaching\n );\n }\n\n /**\n * @notice Batch-initializes protection for multiple assets in a single transaction\n * @param tokenConfigs_ Array of token config inputs, one per asset\n * @custom:access Only Governance\n * @custom:error InvalidArrayLength if the input array is empty\n * @custom:event ProtectionInitialized for each asset\n * @custom:event BoundedPricingWhitelistUpdated for each asset\n */\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external {\n _checkAccessAllowed(\"setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])\");\n uint256 len = tokenConfigs_.length;\n if (len == 0) revert InvalidArrayLength();\n\n for (uint256 i; i < len; ++i) {\n TokenConfigInput calldata tokenConfig = tokenConfigs_[i];\n _setTokenConfig(\n tokenConfig.asset,\n tokenConfig.cooldownPeriod,\n tokenConfig.triggerThreshold,\n tokenConfig.resetThreshold,\n tokenConfig.enableBoundedPricing,\n tokenConfig.enableCaching\n );\n }\n }\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds\n * @custom:access Only Governance\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external {\n _checkAccessAllowed(\"setCooldownPeriod(address,uint64)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(newCooldown);\n\n MarketProtectionState storage state = _ensureInitialized(asset);\n emit CooldownPeriodSet(asset, state.cooldownPeriod, newCooldown);\n state.cooldownPeriod = newCooldown;\n }\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external {\n _checkAccessAllowed(\"setThresholds(address,uint256,uint256)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(newTriggerThreshold);\n ensureNonzeroValue(newResetThreshold);\n if (newTriggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(newTriggerThreshold, MIN_THRESHOLD);\n if (newTriggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(newTriggerThreshold, MAX_THRESHOLD);\n if (newResetThreshold >= newTriggerThreshold) revert InvalidResetThreshold(newResetThreshold);\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (newTriggerThreshold != state.triggerThreshold) {\n emit TriggerThresholdSet(asset, state.triggerThreshold, newTriggerThreshold);\n state.triggerThreshold = uint128(newTriggerThreshold);\n }\n if (newResetThreshold != state.resetThreshold) {\n emit ResetThresholdSet(asset, state.resetThreshold, newResetThreshold);\n state.resetThreshold = uint128(newResetThreshold);\n }\n }\n\n /**\n * @notice Sets whether an asset is enabled for bounded pricing\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external {\n _checkAccessAllowed(\"setAssetBoundedPricingEnabled(address,bool)\");\n ensureNonzeroAddress(asset);\n\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (!enabled && state.currentlyUsingProtectedPrice) {\n revert ProtectedPriceActive(asset);\n }\n\n if (state.isBoundedPricingEnabled == enabled) return;\n\n // reset the window if re-enabling\n if (enabled) {\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\n _setMinPrice(state, asset, spotU128);\n _setMaxPrice(state, asset, spotU128);\n }\n\n state.isBoundedPricingEnabled = enabled;\n emit BoundedPricingWhitelistUpdated(asset, enabled);\n }\n\n /**\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\n * live spot instead of reading or writing the transient slots. The initial value is\n * set via the `enableCaching` argument of `setTokenConfig`.\n * @param asset The underlying asset address\n * @param enabled Whether transient caching is enabled for this asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CachingEnabledUpdated\n */\n function setCachingEnabled(address asset, bool enabled) external {\n _checkAccessAllowed(\"setCachingEnabled(address,bool)\");\n MarketProtectionState storage state = _ensureInitialized(asset);\n emit CachingEnabledUpdated(asset, state.cachingEnabled, enabled);\n state.cachingEnabled = enabled;\n }\n\n // ----- View helpers -----\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory) {\n return allAssets;\n }\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool) {\n return assetProtectionConfig[asset].isBoundedPricingEnabled;\n }\n\n /**\n * @notice Checks if the asset is currently using the protected (bounded) price\n * @param asset The underlying asset address\n * @return True if the asset is currently using the protected price instead of spot\n */\n function currentlyUsingProtectedPrice(address asset) external view returns (bool) {\n return assetProtectionConfig[asset].currentlyUsingProtectedPrice;\n }\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @dev Iterates the append-only allAssets array and filters by isBoundedPricingEnabled.\n * Gas-free for off-chain callers.\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory) {\n uint256 len = allAssets.length;\n address[] memory temp = new address[](len);\n uint256 count;\n for (uint256 i; i < len; ++i) {\n if (assetProtectionConfig[allAssets[i]].isBoundedPricingEnabled) {\n temp[count++] = allAssets[i];\n }\n }\n address[] memory result = new address[](count);\n for (uint256 i; i < count; ++i) {\n result[i] = temp[i];\n }\n return result;\n }\n\n /**\n * @notice Checks if protection can be exited for an asset\n * @dev Returns true when both conditions are met:\n * 1. Cooldown period has elapsed since last trigger\n * 2. Price range has converged below exit threshold\n * @param asset The underlying asset address\n * @return True if protection can be disabled\n */\n function canExitProtection(address asset) external view returns (bool) {\n MarketProtectionState storage state = assetProtectionConfig[asset];\n return\n state.currentlyUsingProtectedPrice &&\n block.timestamp >= uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod) &&\n _computePriceBoundRatio(state.minPrice, state.maxPrice) < state.resetThreshold;\n }\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the deadband\n * from the keeper's proposed window values\n * @dev Allows the keeper to identify stale windows in a single call, avoiding N individual reads.\n * Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's off-chain window minimum prices\n * @param proposedMaxs Keeper's off-chain window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate) {\n uint256 len = assets.length;\n if (len != proposedMins.length || len != proposedMaxs.length) revert InvalidArrayLength();\n\n needsMinUpdate = new bool[](len);\n needsMaxUpdate = new bool[](len);\n\n for (uint256 i; i < len; ++i) {\n MarketProtectionState storage state = assetProtectionConfig[assets[i]];\n needsMinUpdate[i] = _exceedsCorrectionDeadband(state.minPrice, proposedMins[i]);\n needsMaxUpdate[i] = _exceedsCorrectionDeadband(state.maxPrice, proposedMaxs[i]);\n }\n }\n\n // ----- Internal functions -----\n\n /**\n * @notice Initializes protection parameters and price window for a single asset\n * @dev Fetches the current spot price from ResilientOracle to seed the initial min/max window,\n * confirming the oracle is live for this asset before it is listed. Both bounds start at\n * spot so the window expands naturally as prices move. Can only be called once per asset.\n * @param asset The underlying asset address\n * @param cooldownPeriod Minimum time protection stays active after last trigger\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\n * @param enableBoundedPricing Whether to enable bounded pricing immediately upon initialization\n * @param enableCaching Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n */\n function _setTokenConfig(\n address asset,\n uint64 cooldownPeriod,\n uint256 triggerThreshold,\n uint256 resetThreshold,\n bool enableBoundedPricing,\n bool enableCaching\n ) internal {\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(cooldownPeriod);\n ensureNonzeroValue(triggerThreshold);\n ensureNonzeroValue(resetThreshold);\n if (assetProtectionConfig[asset].asset != address(0)) revert MarketAlreadyInitialized(asset);\n if (triggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(triggerThreshold, MIN_THRESHOLD);\n if (triggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(triggerThreshold, MAX_THRESHOLD);\n if (resetThreshold >= triggerThreshold) revert InvalidResetThreshold(resetThreshold);\n if (asset == vai) revert VAINotAllowed();\n\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\n\n assetProtectionConfig[asset] = MarketProtectionState({\n minPrice: spotU128,\n maxPrice: spotU128,\n currentlyUsingProtectedPrice: false,\n isBoundedPricingEnabled: enableBoundedPricing,\n lastProtectionTriggeredAt: 0,\n cooldownPeriod: cooldownPeriod,\n asset: asset,\n triggerThreshold: uint128(triggerThreshold),\n resetThreshold: uint128(resetThreshold),\n cachingEnabled: enableCaching\n });\n\n allAssets.push(asset);\n\n emit ProtectionInitialized(asset, spotU128, spotU128, cooldownPeriod, triggerThreshold);\n emit BoundedPricingWhitelistUpdated(asset, enableBoundedPricing);\n }\n\n /**\n * @notice Validates and applies a keeper-provided min or max price update\n * @param asset The underlying asset address\n * @param newPrice The new price value to set\n * @param boundType Whether this is a MIN or MAX bound update\n * @custom:error ZeroPriceNotAllowed if newPrice is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if boundType is MIN and newPrice exceeds the current spot or is strictly above maxPrice\n * @custom:error InvalidMaxPrice if boundType is MAX and newPrice is below the current spot or is strictly below minPrice\n */\n function _validateAndUpdateBound(address asset, uint128 newPrice, PriceBoundType boundType) internal {\n ensureNonzeroAddress(asset);\n if (newPrice == 0) revert ZeroPriceNotAllowed();\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n uint256 currentSpot = _fetchSpotPrice(asset);\n if (boundType == PriceBoundType.MIN) {\n if (newPrice > state.maxPrice || uint256(newPrice) > currentSpot)\n revert InvalidMinPrice(asset, newPrice, currentSpot);\n _setMinPrice(state, asset, newPrice);\n } else if (boundType == PriceBoundType.MAX) {\n if (newPrice < state.minPrice || uint256(newPrice) < currentSpot)\n revert InvalidMaxPrice(asset, newPrice, currentSpot);\n _setMaxPrice(state, asset, newPrice);\n }\n }\n\n /**\n * @notice Clears protection for an asset once cooldown has elapsed and the window has converged\n * @dev Shared body of `exitProtectionMode` and the ExitProtectionMode branch of `syncPriceBoundsAndProtections`.\n * Callers are responsible for ACM gating before invoking this helper.\n * @param asset The underlying asset address\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\n */\n function _exitProtectionMode(address asset) internal {\n ensureNonzeroAddress(asset);\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (!state.currentlyUsingProtectedPrice) revert ProtectedPriceInactive(asset);\n\n if (block.timestamp < uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod)) {\n revert CooldownNotElapsed(asset, state.lastProtectionTriggeredAt, state.cooldownPeriod);\n }\n\n uint256 rangeRatio = _computePriceBoundRatio(state.minPrice, state.maxPrice);\n if (rangeRatio >= state.resetThreshold) {\n revert PriceRangeNotConverged(asset, rangeRatio, state.resetThreshold);\n }\n\n state.currentlyUsingProtectedPrice = false;\n state.lastProtectionTriggeredAt = 0;\n emit ProtectionModeExited(asset);\n }\n\n /**\n * @notice Shared non-view logic for all bounded price functions.\n * Fetches spot, updates window, triggers protection if needed, and returns both bounded prices.\n * @param vToken vToken address\n * @return minPrice The bounded lower (collateral) price\n * @return maxPrice The bounded upper (debt) price\n */\n function _updateAndGetBoundedPrices(address vToken) internal returns (uint256 minPrice, uint256 maxPrice) {\n address asset = _getUnderlyingAsset(vToken);\n\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\n (minPrice, maxPrice) = _getCachedPrices(asset);\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\n\n // return early if failure from resilient oracle to prevent cold SLOAD\n uint256 spot = _fetchSpotPrice(asset);\n MarketProtectionState storage state = assetProtectionConfig[asset];\n if (!state.isBoundedPricingEnabled) {\n _setCachedPrices(asset, spot, spot);\n return (spot, spot);\n }\n (uint128 updatedMin, uint128 updatedMax, bool windowExpanded) = _expandPriceWindow(state, spot, asset);\n bool protectionActive = _checkAndTriggerProtection(state, spot, asset, windowExpanded);\n (minPrice, maxPrice) = _resolveBoundedPrices(protectionActive, spot, uint256(updatedMin), uint256(updatedMax));\n _setCachedPrices(asset, minPrice, maxPrice);\n }\n\n /**\n * @dev Expands the price window toward extremes if the spot price is a new min or max\n * @param state The market protection state\n * @param spot The current spot price\n * @param asset The underlying asset address (for event emission)\n */\n function _expandPriceWindow(\n MarketProtectionState storage state,\n uint256 spot,\n address asset\n ) internal returns (uint128, uint128, bool) {\n uint128 spotU128 = _safeToUint128(spot);\n uint128 currentMin = state.minPrice;\n uint128 currentMax = state.maxPrice;\n bool windowExpanded;\n if (spotU128 < currentMin) {\n _setMinPrice(state, asset, spotU128);\n currentMin = spotU128;\n windowExpanded = true;\n }\n if (spotU128 > currentMax) {\n _setMaxPrice(state, asset, spotU128);\n currentMax = spotU128;\n windowExpanded = true;\n }\n return (currentMin, currentMax, windowExpanded);\n }\n\n /**\n * @dev Checks if the spot price has deviated beyond the threshold and triggers protection.\n * `lastProtectionTriggeredAt` is reset only on the first trigger or when the price has made a\n * genuine new extreme this update (windowExpanded == true). Recovery within the existing window\n * keeps the cooldown ticking so `exitProtectionMode` remains reachable.\n * @param state The market protection state\n * @param spot The current spot price\n * @param asset The underlying asset address (for event emission)\n * @param windowExpanded True if `_expandPriceWindow` recorded a new low or new high this call\n */\n function _checkAndTriggerProtection(\n MarketProtectionState storage state,\n uint256 spot,\n address asset,\n bool windowExpanded\n ) internal returns (bool triggered) {\n if (_exceedsDeviationThreshold(spot, state.minPrice, state.maxPrice, state.triggerThreshold)) {\n bool enteringProtection = !state.currentlyUsingProtectedPrice;\n if (enteringProtection || windowExpanded) {\n state.lastProtectionTriggeredAt = uint64(block.timestamp);\n }\n if (enteringProtection) {\n state.currentlyUsingProtectedPrice = true;\n }\n emit ProtectionTriggered(asset, spot, state.minPrice, state.maxPrice);\n return true;\n }\n if (state.currentlyUsingProtectedPrice) return true;\n }\n\n /**\n * @notice Resolves the final bounded collateral and debt prices given a spot, window bounds, and protection flag.\n * @dev When protection is active: collateral = min(spot, windowMin), debt = max(spot, windowMax).\n * When protection is inactive: both return spot.\n * @param protectionActive Whether the market protection window is currently active\n * @param spot The current spot price\n * @param windowMin The lower bound of the price window\n * @param windowMax The upper bound of the price window\n * @return minPrice The resolved lower-bound (collateral) price\n * @return maxPrice The resolved upper-bound (debt) price\n */\n function _resolveBoundedPrices(\n bool protectionActive,\n uint256 spot,\n uint256 windowMin,\n uint256 windowMax\n ) internal pure returns (uint256, uint256) {\n if (!protectionActive) return (spot, spot);\n return (spot < windowMin ? spot : windowMin, spot > windowMax ? spot : windowMax);\n }\n\n /**\n * @notice Shared view logic for all bounded price view functions.\n * Checks transient cache first for an early return; on miss, fetches from oracle\n * and computes both prices without state mutations.\n * @param vToken vToken address\n * @return minPrice The bounded lower (collateral) price\n * @return maxPrice The bounded upper (debt) price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function _computeBoundedPrices(address vToken) internal view returns (uint256 minPrice, uint256 maxPrice) {\n address asset = _getUnderlyingAsset(vToken);\n\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\n (minPrice, maxPrice) = _getCachedPrices(asset);\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\n\n // Cache miss — fetch from oracle and compute without state mutations\n uint256 spot = _fetchSpotPrice(asset);\n MarketProtectionState storage state = assetProtectionConfig[asset];\n if (!state.isBoundedPricingEnabled) return (spot, spot);\n\n // Mirror _expandPriceWindow logic: compute what the window would be after expansion\n uint128 spotU128 = _safeToUint128(spot);\n uint128 windowMin128 = spot < uint256(state.minPrice) ? spotU128 : state.minPrice;\n uint128 windowMax128 = spot > uint256(state.maxPrice) ? spotU128 : state.maxPrice;\n\n bool shouldProtect = state.currentlyUsingProtectedPrice ||\n _exceedsDeviationThreshold(spot, windowMin128, windowMax128, state.triggerThreshold);\n\n (minPrice, maxPrice) = _resolveBoundedPrices(shouldProtect, spot, uint256(windowMin128), uint256(windowMax128));\n }\n\n /**\n * @dev Computes the relative spread between the price window bounds as a ratio scaled by EXP_SCALE.\n * Formula: \\((maxPrice - minPrice) / minPrice\\), scaled by `EXP_SCALE`.\n * Used to measure how much the window has converged -- compared against `resetThreshold`\n * to determine whether the price window is tight enough to exit protection mode.\n * @param minPrice The minimum price in the window\n * @param maxPrice The maximum price in the window\n * @return The scaled bound ratio \\(((max - min) * EXP_SCALE) / min\\)\n */\n function _computePriceBoundRatio(uint128 minPrice, uint128 maxPrice) internal pure returns (uint256) {\n uint256 range = uint256(maxPrice) - uint256(minPrice);\n return (range * EXP_SCALE) / uint256(minPrice);\n }\n\n /**\n * @notice Checks whether the spot price has moved beyond the threshold relative to the\n * opposite window bound — i.e. `spot > minPrice * (1 + threshold)` or\n * `spot < maxPrice * (1 - threshold)`.\n * @dev Pump detection: spot > minPrice * (1 + threshold)\n * Crash detection: spot < maxPrice * (1 - threshold)\n * @param spot The current spot price\n * @param minPrice The minimum price in the window\n * @param maxPrice The maximum price in the window\n * @param threshold The deviation threshold (mantissa)\n * @return True if deviation is triggered\n */\n function _exceedsDeviationThreshold(\n uint256 spot,\n uint128 minPrice,\n uint128 maxPrice,\n uint256 threshold\n ) internal pure returns (bool) {\n uint256 upperBound = (uint256(minPrice) * (EXP_SCALE + threshold)) / EXP_SCALE;\n uint256 lowerBound = (uint256(maxPrice) * (EXP_SCALE - threshold)) / EXP_SCALE;\n return (spot > upperBound || spot < lowerBound);\n }\n\n /**\n * @dev Returns true if the relative drift between onChain and proposed exceeds KEEPER_DEADBAND\n * @param currentPrice The current on-chain price\n * @param proposedPrice The keeper's proposed price\n * @return True if drift exceeds deadband\n */\n function _exceedsCorrectionDeadband(uint128 currentPrice, uint128 proposedPrice) internal pure returns (bool) {\n if (currentPrice == 0 || proposedPrice == 0) return false;\n uint256 diff = currentPrice > proposedPrice\n ? uint256(currentPrice - proposedPrice)\n : uint256(proposedPrice - currentPrice);\n return (diff * EXP_SCALE) / uint256(currentPrice) > KEEPER_DEADBAND;\n }\n\n /**\n * @dev Sets the minimum price in the window and emits MinPriceUpdated\n * @param state The market protection state\n * @param asset The underlying asset address (for event emission)\n * @param newMin The new minimum price\n */\n function _setMinPrice(MarketProtectionState storage state, address asset, uint128 newMin) internal {\n emit MinPriceUpdated(asset, state.minPrice, newMin);\n state.minPrice = newMin;\n }\n\n /**\n * @dev Sets the maximum price in the window and emits MaxPriceUpdated\n * @param state The market protection state\n * @param asset The underlying asset address (for event emission)\n * @param newMax The new maximum price\n */\n function _setMaxPrice(MarketProtectionState storage state, address asset, uint128 newMax) internal {\n emit MaxPriceUpdated(asset, state.maxPrice, newMax);\n state.maxPrice = newMax;\n }\n\n /**\n * @dev Writes both lower and upper bounded prices to transient storage. No-ops when the\n * asset's `cachingEnabled` flag is `false`, so callers that disable caching always\n * fall through to live recomputation on subsequent reads.\n * @param asset The underlying asset address\n * @param minPrice The resolved lower (collateral) price to cache\n * @param maxPrice The resolved upper (debt) price to cache\n */\n function _setCachedPrices(address asset, uint256 minPrice, uint256 maxPrice) internal {\n if (!assetProtectionConfig[asset].cachingEnabled) return;\n Transient.cachePrice(COLLATERAL_PRICE_CACHE_SLOT, asset, minPrice);\n Transient.cachePrice(DEBT_PRICE_CACHE_SLOT, asset, maxPrice);\n }\n\n /**\n * @dev Reads a cached final price from transient storage. Returns `(0, 0)` when the\n * asset's `cachingEnabled` flag is `false`, which callers already treat as a cache\n * miss and handle via live recomputation.\n * @param asset The underlying asset address\n * @return minPrice The cached minimum price, or 0 on cache miss\n * @return maxPrice The cached maximum price, or 0 on cache miss\n */\n function _getCachedPrices(address asset) internal view returns (uint256 minPrice, uint256 maxPrice) {\n if (!assetProtectionConfig[asset].cachingEnabled) return (0, 0);\n minPrice = Transient.readCachedPrice(COLLATERAL_PRICE_CACHE_SLOT, asset);\n maxPrice = Transient.readCachedPrice(DEBT_PRICE_CACHE_SLOT, asset);\n }\n\n /**\n * @dev This function returns the underlying asset of a vToken\n * @param vToken vToken address\n * @return asset underlying asset address\n */\n function _getUnderlyingAsset(address vToken) private view returns (address asset) {\n ensureNonzeroAddress(vToken);\n if (vToken == nativeMarket) {\n asset = NATIVE_TOKEN_ADDR;\n } else if (vToken == vai) {\n asset = vai;\n } else {\n asset = VBep20Interface(vToken).underlying();\n }\n }\n\n /**\n * @dev Reverts if the market has not been initialized via setTokenConfig\n * @param asset The underlying asset address\n * @return state The market protection state storage pointer\n */\n function _ensureInitialized(address asset) internal view returns (MarketProtectionState storage state) {\n state = assetProtectionConfig[asset];\n if (state.asset == address(0)) revert MarketNotInitialized(asset);\n }\n\n /**\n * @notice Fetches the current spot price for an asset from the ResilientOracle\n * @param asset The underlying asset address\n * @return The current spot price\n */\n function _fetchSpotPrice(address asset) internal view returns (uint256) {\n return RESILIENT_ORACLE.getPrice(asset);\n }\n\n /**\n * @dev Safely casts a uint256 to uint128, reverting on overflow\n * @param value The value to cast\n * @return The value as uint128\n */\n function _safeToUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) revert PriceExceedsUint128(value);\n return uint128(value);\n }\n}\n" + }, + "contracts/hardhat-dependency-compiler/@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport '@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol';\n" + }, + "contracts/hardhat-dependency-compiler/hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport 'hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol';\n" + }, + "contracts/hardhat-dependency-compiler/hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport 'hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol';\n" + }, + "contracts/interfaces/FeedRegistryInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface FeedRegistryInterface {\n function latestRoundDataByName(\n string memory base,\n string memory quote\n )\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);\n\n function decimalsByName(string memory base, string memory quote) external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAccountant.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAccountant {\n function getRateSafe() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IAnkrBNB.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAnkrBNB {\n function sharesToBonds(uint256 amount) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAsBNB.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAsBNB {\n function minter() external view returns (address);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAsBNBMinter.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAsBNBMinter {\n function convertToTokens(uint256 amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICappedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ICappedOracle {\n function updateSnapshot() external;\n}\n" + }, + "contracts/interfaces/IDeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IDeviationBoundedOracle {\n // --- Enums ---\n\n /// @notice Identifies whether a price bound is a minimum or maximum\n enum PriceBoundType {\n MIN,\n MAX\n }\n\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\n enum KeeperAction {\n SetMinPrice,\n SetMaxPrice,\n ExitProtectionMode\n }\n\n // --- Structs ---\n\n /// @notice Per-asset protection state tracking the min/max price window\n struct MarketProtectionState {\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\n uint128 minPrice;\n /// @notice Highest price observed in the current window\n uint128 maxPrice;\n /// @notice Whether protected price is currently being used\n bool currentlyUsingProtectedPrice;\n /// @notice Whether this market is whitelisted for bounded pricing\n bool isBoundedPricingEnabled;\n /// @notice Timestamp of the last protection trigger — reset on every trigger\n uint64 lastProtectionTriggeredAt;\n /// @notice Minimum time protection stays active after last trigger\n uint64 cooldownPeriod;\n /// @notice The underlying asset address, used to verify initialization\n address asset;\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\n uint128 triggerThreshold;\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\n uint128 resetThreshold;\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n bool cachingEnabled;\n }\n\n /// @notice One item in an syncPriceBoundsAndProtections payload\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\n struct KeeperActionItem {\n address asset;\n KeeperAction action;\n uint256 value;\n }\n\n /// @notice One item in a setTokenConfigs payload\n struct TokenConfigInput {\n /// @notice The underlying asset address\n address asset;\n /// @notice Minimum time protection stays active after the last trigger (seconds)\n uint64 cooldownPeriod;\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\n uint256 triggerThreshold;\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\n uint256 resetThreshold;\n /// @notice Whether to enable bounded pricing immediately upon initialization\n bool enableBoundedPricing;\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n bool enableCaching;\n }\n\n // --- Events ---\n\n /// @notice Emitted when protection is initialized for an asset\n event ProtectionInitialized(\n address indexed asset,\n uint128 minPrice,\n uint128 maxPrice,\n uint64 cooldownPeriod,\n uint256 triggerThreshold\n );\n\n /// @notice Emitted when protection mode is triggered for an asset\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\n\n /// @notice Emitted when protection mode is disabled for an asset\n event ProtectionModeExited(address indexed asset);\n\n /// @notice Emitted when the keeper updates the minimum price for an asset\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\n\n /// @notice Emitted when the keeper updates the maximum price for an asset\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\n\n /// @notice Emitted when the entry threshold is updated for an asset\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\n\n /// @notice Emitted when the exit threshold is updated for an asset\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\n\n /// @notice Emitted when the cooldown period is updated for an asset\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\n\n /// @notice Emitted when an asset's whitelist status changes\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\n\n /// @notice Emitted when the per-asset transient caching flag is toggled\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\n\n // --- Errors ---\n\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\n error MarketNotInitialized(address asset);\n\n /// @notice Thrown when trying to initialize an already initialized market\n error MarketAlreadyInitialized(address asset);\n\n /// @notice Thrown when trying to disable protection that is not active\n error ProtectedPriceInactive(address asset);\n\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\n\n /// @notice Thrown when trying to disable protection before price range has converged\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\n\n /// @notice Thrown when keeper tries to set minPrice above current spot\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\n\n /// @notice Thrown when keeper tries to set maxPrice below current spot\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\n\n /// @notice Thrown when threshold is set below the minimum allowed value\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\n\n /// @notice Thrown when threshold is set above the maximum allowed value\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\n\n /// @notice Thrown when a price exceeds uint128 max\n error PriceExceedsUint128(uint256 price);\n\n /// @notice Thrown when a zero price is provided where a non-zero price is required\n error ZeroPriceNotAllowed();\n\n /// @notice Thrown when trying to initialize protection for VAI\n error VAINotAllowed();\n\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\n error ProtectedPriceActive(address asset);\n\n /// @notice Thrown when the lengths of the arrays are not equal\n error InvalidArrayLength();\n\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\n error InvalidResetThreshold(uint256 resetThreshold);\n\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\n error InvalidKeeperAction(uint8 action);\n\n // --- Non-view price functions (update window + trigger protection) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- State update (call before view price reads to populate transient cache) ---\n\n /**\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\n * reads in the same transaction are served from transient storage. The transient\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\n * @param vToken vToken address\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external;\n\n // --- View price functions (read stored/cached state only) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return price The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return price The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- Keeper functions ---\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMin is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external;\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMax is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external;\n\n /**\n * @notice Exits protection mode for a given asset once conditions are met\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\n * @custom:event ProtectionModeExited\n */\n function exitProtectionMode(address asset) external;\n\n /**\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\n * Empty `actions` is a no-op success.\n * @param actions The list of keeper actions to apply\n * @custom:access Only authorized keeper addresses\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\n */\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\n\n // --- Admin functions (governance-gated) ---\n\n /**\n * @notice Initializes protection parameters for a new asset\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\n * confirming the oracle is live for this asset before it is listed.\n * @param tokenConfig_ Token config input for the asset\n * @custom:access Only Governance\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\n\n /**\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\n * @param tokenConfigs_ Array of token config inputs, one per asset\n * @custom:access Only Governance\n * @custom:error InvalidArrayLength if the input array is empty\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\n * @custom:error VAINotAllowed if any asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\n * @custom:event ProtectionInitialized for each asset\n * @custom:event BoundedPricingWhitelistUpdated for each asset\n */\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds; must be non-zero\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\n\n /**\n * @notice Sets whether bounded pricing is enabled for an asset\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\n\n /**\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\n * live spot instead of reading or writing the transient slots. The initial value is\n * set via the `enableCaching` argument of `setTokenConfig`.\n * @param asset The underlying asset address\n * @param enabled Whether transient caching is enabled for this asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CachingEnabledUpdated\n */\n function setCachingEnabled(address asset, bool enabled) external;\n\n // --- View helpers ---\n\n /**\n * @notice Returns the full protection state for an asset\n * @param asset The underlying asset address\n * @return minPrice Lowest price observed in the current window\n * @return maxPrice Highest price observed in the current window\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\n * @return cooldownPeriod Minimum time protection stays active after last trigger\n * @return assetAddr The underlying asset address stored in the struct\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\n */\n function assetProtectionConfig(\n address asset\n )\n external\n view\n returns (\n uint128 minPrice,\n uint128 maxPrice,\n bool currentlyUsingProtectedPrice,\n bool isBoundedPricingEnabled,\n uint64 lastProtectionTriggeredAt,\n uint64 cooldownPeriod,\n address assetAddr,\n uint128 triggerThreshold,\n uint128 resetThreshold,\n bool cachingEnabled\n );\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool);\n\n /**\n * @notice Checks if the asset is currently using the protected (bounded) price\n * @param asset The underlying asset address\n * @return True if the asset is currently using the protected price instead of spot\n */\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\n\n /**\n * @notice Checks if protection can be exited for a given asset\n * @param asset The underlying asset address\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\n */\n function canExitProtection(address asset) external view returns (bool);\n\n /**\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\n * @param index Array index\n * @return The asset address at the given index\n */\n function allAssets(uint256 index) external view returns (address);\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory);\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's proposed window minimum prices\n * @param proposedMaxs Keeper's proposed window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\n}\n" + }, + "contracts/interfaces/IERC4626.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IERC4626 {\n function convertToAssets(uint256 shares) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IEtherFiLiquidityPool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IEtherFiLiquidityPool {\n function amountForShare(uint256 _share) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IPendlePtOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IPendlePtOracle {\n function getPtToAssetRate(address market, uint32 duration) external view returns (uint256);\n\n function getPtToSyRate(address market, uint32 duration) external view returns (uint256);\n\n function getOracleState(\n address market,\n uint32 duration\n )\n external\n view\n returns (bool increaseCardinalityRequired, uint16 cardinalityRequired, bool oldestObservationSatisfied);\n}\n" + }, + "contracts/interfaces/IPStakePool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IPStakePool {\n struct Data {\n uint256 totalWei;\n uint256 poolTokenSupply;\n }\n\n /**\n * @dev The current exchange rate for converting stkBNB to BNB.\n */\n function exchangeRate() external view returns (Data memory);\n}\n" + }, + "contracts/interfaces/ISFrax.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISFrax {\n function convertToAssets(uint256 shares) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/ISfrxEthFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISfrxEthFraxOracle {\n function getPrices() external view returns (bool _isbadData, uint256 _priceLow, uint256 _priceHigh);\n}\n" + }, + "contracts/interfaces/IStaderStakeManager.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IStaderStakeManager {\n function convertBnbXToBnb(uint256 _amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IStETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface IStETH {\n function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/ISynclubStakeManager.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISynclubStakeManager {\n function convertSnBnbToBnb(uint256 _amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IWBETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IWBETH {\n function exchangeRate() external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IZkETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface IZkETH {\n function LSTPerToken() external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/OracleInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface OracleInterface {\n function getPrice(address asset) external view returns (uint256);\n}\n\ninterface ResilientOracleInterface is OracleInterface {\n function updatePrice(address vToken) external;\n\n function updateAssetPrice(address asset) external;\n\n function getUnderlyingPrice(address vToken) external view returns (uint256);\n}\n\ninterface BoundValidatorInterface {\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool);\n}\n" + }, + "contracts/interfaces/PublicResolverInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity ^0.8.25;\n\ninterface PublicResolverInterface {\n function addr(bytes32 node) external view returns (address payable);\n}\n" + }, + "contracts/interfaces/SIDRegistryInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity ^0.8.25;\n\ninterface SIDRegistryInterface {\n function resolver(bytes32 node) external view returns (address);\n}\n" + }, + "contracts/interfaces/VBep20Interface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\ninterface VBep20Interface is IERC20Metadata {\n /**\n * @notice Underlying asset for this VToken\n */\n function underlying() external view returns (address);\n}\n" + }, + "contracts/lib/Transient.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nlibrary Transient {\n /**\n * @notice Cache the asset price into transient storage\n * @param key address of the asset\n * @param value asset price\n */\n function cachePrice(bytes32 cacheSlot, address key, uint256 value) internal {\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\n assembly (\"memory-safe\") {\n tstore(slot, value)\n }\n }\n\n /**\n * @notice Read cached price from transient storage\n * @param key address of the asset\n * @return value cached asset price\n */\n function readCachedPrice(bytes32 cacheSlot, address key) internal view returns (uint256 value) {\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\n assembly (\"memory-safe\") {\n value := tload(slot)\n }\n }\n}\n" + }, + "contracts/oracles/AnkrBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IAnkrBNB } from \"../interfaces/IAnkrBNB.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title AnkrBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of ankrBNB asset\n */\ncontract AnkrBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address ankrBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n ankrBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of BNB for 1 ankrBNB\n * @return amount The amount of BNB for ankrBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IAnkrBNB(CORRELATED_TOKEN).sharesToBonds(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/AsBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IAsBNB } from \"../interfaces/IAsBNB.sol\";\nimport { IAsBNBMinter } from \"../interfaces/IAsBNBMinter.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title asBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of asBNB asset\n */\ncontract AsBNBOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address asBNB,\n address slisBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n asBNB,\n slisBNB,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of slisBNB for 1 asBNB\n * @return price The amount of slisBNB for asBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n IAsBNBMinter minter = IAsBNBMinter(IAsBNB(CORRELATED_TOKEN).minter());\n return minter.convertToTokens(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/BinanceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/SIDRegistryInterface.sol\";\nimport \"../interfaces/FeedRegistryInterface.sol\";\nimport \"../interfaces/PublicResolverInterface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title BinanceOracle\n * @author Venus\n * @notice This oracle fetches price of assets from Binance.\n */\ncontract BinanceOracle is AccessControlledV8, OracleInterface {\n /// @notice Used to fetch feed registry address.\n address public sidRegistryAddress;\n\n /// @notice Set this as asset address for BNB. This is the underlying address for vBNB\n address public constant BNB_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Max stale period configuration for assets\n mapping(string => uint256) public maxStalePeriod;\n\n /// @notice Override symbols to be compatible with Binance feed registry\n mapping(string => string) public symbols;\n\n /// @notice Used to fetch price of assets used directly when space ID is not supported by current chain.\n address public feedRegistryAddress;\n\n /// @notice Emits when asset stale period is updated.\n event MaxStalePeriodAdded(string indexed asset, uint256 maxStalePeriod);\n\n /// @notice Emits when symbol of the asset is updated.\n event SymbolOverridden(string indexed symbol, string overriddenSymbol);\n\n /// @notice Emits when address of feed registry is updated.\n event FeedRegistryUpdated(address indexed oldFeedRegistry, address indexed newFeedRegistry);\n\n /**\n * @notice Checks whether an address is null or not\n */\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Sets the contracts required to fetch prices\n * @param _sidRegistryAddress Address of SID registry\n * @param _acm Address of the access control manager contract\n */\n function initialize(address _sidRegistryAddress, address _acm) external initializer {\n sidRegistryAddress = _sidRegistryAddress;\n __AccessControlled_init(_acm);\n }\n\n /**\n * @notice Used to set the max stale period of an asset\n * @param symbol The symbol of the asset\n * @param _maxStalePeriod The max stake period\n */\n function setMaxStalePeriod(string memory symbol, uint256 _maxStalePeriod) external {\n _checkAccessAllowed(\"setMaxStalePeriod(string,uint256)\");\n if (_maxStalePeriod == 0) revert(\"stale period can't be zero\");\n if (bytes(symbol).length == 0) revert(\"symbol cannot be empty\");\n\n maxStalePeriod[symbol] = _maxStalePeriod;\n emit MaxStalePeriodAdded(symbol, _maxStalePeriod);\n }\n\n /**\n * @notice Used to override a symbol when fetching price\n * @param symbol The symbol to override\n * @param overrideSymbol The symbol after override\n */\n function setSymbolOverride(string calldata symbol, string calldata overrideSymbol) external {\n _checkAccessAllowed(\"setSymbolOverride(string,string)\");\n if (bytes(symbol).length == 0) revert(\"symbol cannot be empty\");\n\n symbols[symbol] = overrideSymbol;\n emit SymbolOverridden(symbol, overrideSymbol);\n }\n\n /**\n * @notice Used to set feed registry address when current chain does not support space ID.\n * @param newfeedRegistryAddress Address of new feed registry.\n */\n function setFeedRegistryAddress(\n address newfeedRegistryAddress\n ) external notNullAddress(newfeedRegistryAddress) onlyOwner {\n if (sidRegistryAddress != address(0)) revert(\"sidRegistryAddress must be zero\");\n emit FeedRegistryUpdated(feedRegistryAddress, newfeedRegistryAddress);\n feedRegistryAddress = newfeedRegistryAddress;\n }\n\n /**\n * @notice Uses Space ID to fetch the feed registry address\n * @return feedRegistryAddress Address of binance oracle feed registry.\n */\n function getFeedRegistryAddress() public view returns (address) {\n bytes32 nodeHash = 0x94fe3821e0768eb35012484db4df61890f9a6ca5bfa984ef8ff717e73139faff;\n\n SIDRegistryInterface sidRegistry = SIDRegistryInterface(sidRegistryAddress);\n address publicResolverAddress = sidRegistry.resolver(nodeHash);\n PublicResolverInterface publicResolver = PublicResolverInterface(publicResolverAddress);\n\n return publicResolver.addr(nodeHash);\n }\n\n /**\n * @notice Gets the price of a asset from the binance oracle\n * @param asset Address of the asset\n * @return Price in USD\n */\n function getPrice(address asset) public view returns (uint256) {\n string memory symbol;\n uint256 decimals;\n\n if (asset == BNB_ADDR) {\n symbol = \"BNB\";\n decimals = 18;\n } else {\n IERC20Metadata token = IERC20Metadata(asset);\n symbol = token.symbol();\n decimals = token.decimals();\n }\n\n string memory overrideSymbol = symbols[symbol];\n\n if (bytes(overrideSymbol).length != 0) {\n symbol = overrideSymbol;\n }\n\n return _getPrice(symbol, decimals);\n }\n\n function _getPrice(string memory symbol, uint256 decimals) internal view returns (uint256) {\n FeedRegistryInterface feedRegistry;\n\n if (sidRegistryAddress != address(0)) {\n // If sidRegistryAddress is available, fetch feedRegistryAddress from sidRegistry\n feedRegistry = FeedRegistryInterface(getFeedRegistryAddress());\n } else {\n // Use feedRegistry directly if sidRegistryAddress is not available\n feedRegistry = FeedRegistryInterface(feedRegistryAddress);\n }\n\n (, int256 answer, , uint256 updatedAt, ) = feedRegistry.latestRoundDataByName(symbol, \"USD\");\n if (answer <= 0) revert(\"invalid binance oracle price\");\n if (block.timestamp < updatedAt) revert(\"updatedAt exceeds block time\");\n\n uint256 deltaTime;\n unchecked {\n deltaTime = block.timestamp - updatedAt;\n }\n if (deltaTime > maxStalePeriod[symbol]) revert(\"binance oracle price expired\");\n\n uint256 decimalDelta = feedRegistry.decimalsByName(symbol, \"USD\");\n return (uint256(answer) * (10 ** (18 - decimalDelta))) * (10 ** (18 - decimals));\n }\n}\n" + }, + "contracts/oracles/BNBxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IStaderStakeManager } from \"../interfaces/IStaderStakeManager.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title BNBxOracle\n * @author Venus\n * @notice This oracle fetches the price of BNBx asset\n */\ncontract BNBxOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakeManager\n IStaderStakeManager public immutable STAKE_MANAGER;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakeManager,\n address bnbx,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n bnbx,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakeManager);\n STAKE_MANAGER = IStaderStakeManager(stakeManager);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 BNBx\n * @return price The amount of BNB for BNBx\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STAKE_MANAGER.convertBnbXToBnb(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/BoundValidator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\n\n/**\n * @title BoundValidator\n * @author Venus\n * @notice The BoundValidator contract is used to validate prices fetched from two different sources.\n * Each asset has an upper and lower bound ratio set in the config. In order for a price to be valid\n * it must fall within this range of the validator price.\n */\ncontract BoundValidator is AccessControlledV8, BoundValidatorInterface {\n struct ValidateConfig {\n /// @notice asset address\n address asset;\n /// @notice Upper bound of deviation between reported price and anchor price,\n /// beyond which the reported price will be invalidated\n uint256 upperBoundRatio;\n /// @notice Lower bound of deviation between reported price and anchor price,\n /// below which the reported price will be invalidated\n uint256 lowerBoundRatio;\n }\n\n /// @notice validation configs by asset\n mapping(address => ValidateConfig) public validateConfigs;\n\n /// @notice Emit this event when new validation configs are added\n event ValidateConfigAdded(address indexed asset, uint256 indexed upperBound, uint256 indexed lowerBound);\n\n /// @notice Constructor for the implementation contract. Sets immutable variables.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the owner of the contract\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n /**\n * @notice Add multiple validation configs at the same time\n * @param configs Array of validation configs\n * @custom:access Only Governance\n * @custom:error Zero length error is thrown if length of the config array is 0\n * @custom:event Emits ValidateConfigAdded for each validation config that is successfully set\n */\n function setValidateConfigs(ValidateConfig[] memory configs) external {\n uint256 length = configs.length;\n if (length == 0) revert(\"invalid validate config length\");\n for (uint256 i; i < length; ++i) {\n setValidateConfig(configs[i]);\n }\n }\n\n /**\n * @notice Add a single validation config\n * @param config Validation config struct\n * @custom:access Only Governance\n * @custom:error Null address error is thrown if asset address is null\n * @custom:error Range error thrown if bound ratio is not positive\n * @custom:error Range error thrown if lower bound is greater than or equal to upper bound\n * @custom:event Emits ValidateConfigAdded when a validation config is successfully set\n */\n function setValidateConfig(ValidateConfig memory config) public {\n _checkAccessAllowed(\"setValidateConfig(ValidateConfig)\");\n\n if (config.asset == address(0)) revert(\"asset can't be zero address\");\n if (config.upperBoundRatio == 0 || config.lowerBoundRatio == 0) revert(\"bound must be positive\");\n if (config.upperBoundRatio <= config.lowerBoundRatio) revert(\"upper bound must be higher than lowner bound\");\n validateConfigs[config.asset] = config;\n emit ValidateConfigAdded(config.asset, config.upperBoundRatio, config.lowerBoundRatio);\n }\n\n /**\n * @notice Test reported asset price against anchor price\n * @param asset asset address\n * @param reportedPrice The price to be tested\n * @custom:error Missing error thrown if asset config is not set\n * @custom:error Price error thrown if anchor price is not valid\n */\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reportedPrice,\n uint256 anchorPrice\n ) public view virtual override returns (bool) {\n if (validateConfigs[asset].upperBoundRatio == 0) revert(\"validation config not exist\");\n if (anchorPrice == 0) revert(\"anchor price is not valid\");\n return _isWithinAnchor(asset, reportedPrice, anchorPrice);\n }\n\n /**\n * @notice Test whether the reported price is within the valid bounds\n * @param asset Asset address\n * @param reportedPrice The price to be tested\n * @param anchorPrice The reported price must be within the the valid bounds of this price\n */\n function _isWithinAnchor(address asset, uint256 reportedPrice, uint256 anchorPrice) private view returns (bool) {\n if (reportedPrice != 0) {\n // we need to multiply anchorPrice by 1e18 to make the ratio 18 decimals\n uint256 anchorRatio = (anchorPrice * 1e18) / reportedPrice;\n uint256 upperBoundAnchorRatio = validateConfigs[asset].upperBoundRatio;\n uint256 lowerBoundAnchorRatio = validateConfigs[asset].lowerBoundRatio;\n return anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio;\n }\n return false;\n }\n\n // BoundValidator is to get inherited, so it's a good practice to add some storage gaps like\n // OpenZepplin proposed in their contracts: https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n // solhint-disable-next-line\n uint256[49] private __gap;\n}\n" + }, + "contracts/oracles/ChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\n\n/**\n * @title ChainlinkOracle\n * @author Venus\n * @notice This oracle fetches prices of assets from the Chainlink oracle.\n */\ncontract ChainlinkOracle is AccessControlledV8, OracleInterface {\n struct TokenConfig {\n /// @notice Underlying token address, which can't be a null address\n /// @notice Used to check if a token is supported\n /// @notice 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB address for native tokens\n /// (e.g BNB for BNB chain, ETH for Ethereum network)\n address asset;\n /// @notice Chainlink feed address\n address feed;\n /// @notice Price expiration period of this asset\n uint256 maxStalePeriod;\n }\n\n /// @notice Set this as asset address for native token on each chain.\n /// This is the underlying address for vBNB on BNB chain or an underlying asset for a native market on any chain.\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Manually set an override price, useful under extenuating conditions such as price feed failure\n mapping(address => uint256) public prices;\n\n /// @notice Token config by assets\n mapping(address => TokenConfig) public tokenConfigs;\n\n /// @notice Emit when a price is manually set\n event PricePosted(address indexed asset, uint256 previousPriceMantissa, uint256 newPriceMantissa);\n\n /// @notice Emit when a token config is added\n event TokenConfigAdded(address indexed asset, address feed, uint256 maxStalePeriod);\n\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the owner of the contract\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n /**\n * @notice Manually set the price of a given asset\n * @param asset Asset address\n * @param price Asset price in 18 decimals\n * @custom:access Only Governance\n * @custom:event Emits PricePosted event on successfully setup of asset price\n */\n function setDirectPrice(address asset, uint256 price) external notNullAddress(asset) {\n _checkAccessAllowed(\"setDirectPrice(address,uint256)\");\n\n uint256 previousPriceMantissa = prices[asset];\n prices[asset] = price;\n emit PricePosted(asset, previousPriceMantissa, price);\n }\n\n /**\n * @notice Add multiple token configs at the same time\n * @param tokenConfigs_ config array\n * @custom:access Only Governance\n * @custom:error Zero length error thrown, if length of the array in parameter is 0\n */\n function setTokenConfigs(TokenConfig[] memory tokenConfigs_) external {\n if (tokenConfigs_.length == 0) revert(\"length can't be 0\");\n uint256 numTokenConfigs = tokenConfigs_.length;\n for (uint256 i; i < numTokenConfigs; ++i) {\n setTokenConfig(tokenConfigs_[i]);\n }\n }\n\n /**\n * @notice Add single token config. asset & feed cannot be null addresses and maxStalePeriod must be positive\n * @param tokenConfig Token config struct\n * @custom:access Only Governance\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error NotNullAddress error is thrown if token feed address is null\n * @custom:error Range error is thrown if maxStale period of token is not greater than zero\n * @custom:event Emits TokenConfigAdded event on successfully setting of the token config\n */\n function setTokenConfig(\n TokenConfig memory tokenConfig\n ) public notNullAddress(tokenConfig.asset) notNullAddress(tokenConfig.feed) {\n _checkAccessAllowed(\"setTokenConfig(TokenConfig)\");\n\n if (tokenConfig.maxStalePeriod == 0) revert(\"stale period can't be zero\");\n tokenConfigs[tokenConfig.asset] = tokenConfig;\n emit TokenConfigAdded(tokenConfig.asset, tokenConfig.feed, tokenConfig.maxStalePeriod);\n }\n\n /**\n * @notice Gets the price of a asset from the chainlink oracle\n * @param asset Address of the asset\n * @return Price in USD from Chainlink or a manually set price for the asset\n */\n function getPrice(address asset) public view virtual returns (uint256) {\n uint256 decimals;\n\n if (asset == NATIVE_TOKEN_ADDR) {\n decimals = 18;\n } else {\n IERC20Metadata token = IERC20Metadata(asset);\n decimals = token.decimals();\n }\n\n return _getPriceInternal(asset, decimals);\n }\n\n /**\n * @notice Gets the Chainlink price for a given asset\n * @param asset address of the asset\n * @param decimals decimals of the asset\n * @return price Asset price in USD or a manually set price of the asset\n */\n function _getPriceInternal(address asset, uint256 decimals) internal view returns (uint256 price) {\n uint256 tokenPrice = prices[asset];\n if (tokenPrice != 0) {\n price = tokenPrice;\n } else {\n price = _getChainlinkPrice(asset);\n }\n\n uint256 decimalDelta = 18 - decimals;\n return price * (10 ** decimalDelta);\n }\n\n /**\n * @notice Get the Chainlink price for an asset, revert if token config doesn't exist\n * @dev The precision of the price feed is used to ensure the returned price has 18 decimals of precision\n * @param asset Address of the asset\n * @return price Price in USD, with 18 decimals of precision\n * @custom:error NotNullAddress error is thrown if the asset address is null\n * @custom:error Price error is thrown if the Chainlink price of asset is not greater than zero\n * @custom:error Timing error is thrown if current timestamp is less than the last updatedAt timestamp\n * @custom:error Timing error is thrown if time difference between current time and last updated time\n * is greater than maxStalePeriod\n */\n function _getChainlinkPrice(\n address asset\n ) private view notNullAddress(tokenConfigs[asset].asset) returns (uint256) {\n TokenConfig memory tokenConfig = tokenConfigs[asset];\n AggregatorV3Interface feed = AggregatorV3Interface(tokenConfig.feed);\n\n // note: maxStalePeriod cannot be 0\n uint256 maxStalePeriod = tokenConfig.maxStalePeriod;\n\n // Chainlink USD-denominated feeds store answers at 8 decimals, mostly\n uint256 decimalDelta = 18 - feed.decimals();\n\n (, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();\n if (answer <= 0) revert(\"chainlink price must be positive\");\n if (block.timestamp < updatedAt) revert(\"updatedAt exceeds block time\");\n\n uint256 deltaTime;\n unchecked {\n deltaTime = block.timestamp - updatedAt;\n }\n\n if (deltaTime > maxStalePeriod) revert(\"chainlink price expired\");\n\n return uint256(answer) * (10 ** decimalDelta);\n }\n}\n" + }, + "contracts/oracles/common/CorrelatedTokenOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OracleInterface, ResilientOracleInterface } from \"../../interfaces/OracleInterface.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { SECONDS_PER_YEAR } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { ICappedOracle } from \"../../interfaces/ICappedOracle.sol\";\nimport { IAccessControlManagerV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\";\n\n/**\n * @title CorrelatedTokenOracle\n * @notice This oracle fetches the price of a token that is correlated to another token.\n */\nabstract contract CorrelatedTokenOracle is OracleInterface, ICappedOracle {\n /// @notice Address of the correlated token\n address public immutable CORRELATED_TOKEN;\n\n /// @notice Address of the underlying token\n address public immutable UNDERLYING_TOKEN;\n\n /// @notice Address of Resilient Oracle\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Address of the AccessControlManager contract\n IAccessControlManagerV8 public immutable ACCESS_CONTROL_MANAGER;\n\n //// @notice Growth rate percentage in seconds. Ex: 1e18 is 100%\n uint256 public growthRatePerSecond;\n\n /// @notice Snapshot update interval\n uint256 public snapshotInterval;\n\n /// @notice Last stored snapshot maximum exchange rate\n uint256 public snapshotMaxExchangeRate;\n\n /// @notice Last stored snapshot timestamp\n uint256 public snapshotTimestamp;\n\n /// @notice Gap to add when updating the snapshot\n uint256 public snapshotGap;\n\n /// @notice Emitted when the snapshot is updated\n event SnapshotUpdated(uint256 indexed maxExchangeRate, uint256 indexed timestamp);\n\n /// @notice Emitted when the growth rate is updated\n event GrowthRateUpdated(\n uint256 indexed oldGrowthRatePerSecond,\n uint256 indexed newGrowthRatePerSecond,\n uint256 indexed oldSnapshotInterval,\n uint256 newSnapshotInterval\n );\n\n /// @notice Emitted when the snapshot gap is updated\n event SnapshotGapUpdated(uint256 indexed oldSnapshotGap, uint256 indexed newSnapshotGap);\n\n /// @notice Thrown if the token address is invalid\n error InvalidTokenAddress();\n\n /// @notice Thrown if the growth rate is invalid\n error InvalidGrowthRate();\n\n /// @notice Thrown if the initial snapshot is invalid\n error InvalidInitialSnapshot();\n\n /// @notice Thrown if the max snapshot exchange rate is invalid\n error InvalidSnapshotMaxExchangeRate();\n\n /// @notice @notice Thrown when the action is prohibited by AccessControlManager\n error Unauthorized(address sender, address calledContract, string methodSignature);\n\n /**\n * @notice Constructor for the implementation contract.\n * @custom:error InvalidGrowthRate error is thrown if the growth rate is invalid\n * @custom:error InvalidInitialSnapshot error is thrown if the initial snapshot values are invalid\n */\n constructor(\n address _correlatedToken,\n address _underlyingToken,\n address _resilientOracle,\n uint256 _annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 _initialSnapshotMaxExchangeRate,\n uint256 _initialSnapshotTimestamp,\n address _accessControlManager,\n uint256 _snapshotGap\n ) {\n growthRatePerSecond = _annualGrowthRate / SECONDS_PER_YEAR;\n\n if ((growthRatePerSecond == 0 && _snapshotInterval > 0) || (growthRatePerSecond > 0 && _snapshotInterval == 0))\n revert InvalidGrowthRate();\n\n if ((_initialSnapshotMaxExchangeRate == 0 || _initialSnapshotTimestamp == 0) && _snapshotInterval > 0) {\n revert InvalidInitialSnapshot();\n }\n\n ensureNonzeroAddress(_correlatedToken);\n ensureNonzeroAddress(_underlyingToken);\n ensureNonzeroAddress(_resilientOracle);\n ensureNonzeroAddress(_accessControlManager);\n\n CORRELATED_TOKEN = _correlatedToken;\n UNDERLYING_TOKEN = _underlyingToken;\n RESILIENT_ORACLE = ResilientOracleInterface(_resilientOracle);\n snapshotInterval = _snapshotInterval;\n\n snapshotMaxExchangeRate = _initialSnapshotMaxExchangeRate;\n snapshotTimestamp = _initialSnapshotTimestamp;\n snapshotGap = _snapshotGap;\n\n ACCESS_CONTROL_MANAGER = IAccessControlManagerV8(_accessControlManager);\n }\n\n /**\n * @notice Directly sets the snapshot exchange rate and timestamp\n * @param _snapshotMaxExchangeRate The exchange rate to set\n * @param _snapshotTimestamp The timestamp to set\n * @custom:event Emits SnapshotUpdated event on successful update of the snapshot\n */\n function setSnapshot(uint256 _snapshotMaxExchangeRate, uint256 _snapshotTimestamp) external {\n _checkAccessAllowed(\"setSnapshot(uint256,uint256)\");\n\n snapshotMaxExchangeRate = _snapshotMaxExchangeRate;\n snapshotTimestamp = _snapshotTimestamp;\n\n emit SnapshotUpdated(snapshotMaxExchangeRate, snapshotTimestamp);\n }\n\n /**\n * @notice Sets the growth rate and snapshot interval\n * @param _annualGrowthRate The annual growth rate to set\n * @param _snapshotInterval The snapshot interval to set\n * @custom:error InvalidGrowthRate error is thrown if the growth rate is invalid\n * @custom:event Emits GrowthRateUpdated event on successful update of the growth rate\n */\n function setGrowthRate(uint256 _annualGrowthRate, uint256 _snapshotInterval) external {\n _checkAccessAllowed(\"setGrowthRate(uint256,uint256)\");\n uint256 oldGrowthRatePerSecond = growthRatePerSecond;\n\n growthRatePerSecond = _annualGrowthRate / SECONDS_PER_YEAR;\n\n if ((growthRatePerSecond == 0 && _snapshotInterval > 0) || (growthRatePerSecond > 0 && _snapshotInterval == 0))\n revert InvalidGrowthRate();\n\n emit GrowthRateUpdated(oldGrowthRatePerSecond, growthRatePerSecond, snapshotInterval, _snapshotInterval);\n\n snapshotInterval = _snapshotInterval;\n }\n\n /**\n * @notice Sets the snapshot gap\n * @param _snapshotGap The snapshot gap to set\n * @custom:event Emits SnapshotGapUpdated event on successful update of the snapshot gap\n */\n function setSnapshotGap(uint256 _snapshotGap) external {\n _checkAccessAllowed(\"setSnapshotGap(uint256)\");\n\n emit SnapshotGapUpdated(snapshotGap, _snapshotGap);\n\n snapshotGap = _snapshotGap;\n }\n\n /**\n * @notice Returns if the price is capped\n * @return isCapped Boolean indicating if the price is capped\n */\n function isCapped() external view virtual returns (bool) {\n if (snapshotInterval == 0) {\n return false;\n }\n\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n if (maxAllowedExchangeRate == 0) {\n return false;\n }\n\n uint256 exchangeRate = getUnderlyingAmount();\n\n return exchangeRate > maxAllowedExchangeRate;\n }\n\n /**\n * @notice Updates the snapshot price and timestamp\n * @custom:event Emits SnapshotUpdated event on successful update of the snapshot\n * @custom:error InvalidSnapshotMaxExchangeRate error is thrown if the max snapshot exchange rate is zero\n */\n function updateSnapshot() public override {\n if (block.timestamp - snapshotTimestamp < snapshotInterval || snapshotInterval == 0) return;\n\n uint256 exchangeRate = getUnderlyingAmount();\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n\n snapshotMaxExchangeRate =\n (exchangeRate > maxAllowedExchangeRate ? maxAllowedExchangeRate : exchangeRate) +\n snapshotGap;\n snapshotTimestamp = block.timestamp;\n\n if (snapshotMaxExchangeRate == 0) revert InvalidSnapshotMaxExchangeRate();\n\n RESILIENT_ORACLE.updateAssetPrice(UNDERLYING_TOKEN);\n emit SnapshotUpdated(snapshotMaxExchangeRate, snapshotTimestamp);\n }\n\n /**\n * @notice Fetches the price of the token\n * @param asset Address of the token\n * @return price The price of the token in scaled decimal places. It can be capped\n * to a maximum value taking into account the growth rate\n * @custom:error InvalidTokenAddress error is thrown if the token address is invalid\n */\n function getPrice(address asset) public view override returns (uint256) {\n if (asset != CORRELATED_TOKEN) revert InvalidTokenAddress();\n\n uint256 exchangeRate = getUnderlyingAmount();\n\n if (snapshotInterval == 0) {\n return _calculatePrice(exchangeRate);\n }\n\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n\n uint256 finalExchangeRate = (exchangeRate > maxAllowedExchangeRate && maxAllowedExchangeRate != 0)\n ? maxAllowedExchangeRate\n : exchangeRate;\n\n return _calculatePrice(finalExchangeRate);\n }\n\n /**\n * @notice Gets the maximum allowed exchange rate for token\n * @return maxExchangeRate Maximum allowed exchange rate\n */\n function getMaxAllowedExchangeRate() public view returns (uint256) {\n uint256 timeElapsed = block.timestamp - snapshotTimestamp;\n uint256 maxExchangeRate = snapshotMaxExchangeRate +\n (snapshotMaxExchangeRate * growthRatePerSecond * timeElapsed) /\n 1e18;\n return maxExchangeRate;\n }\n\n /**\n * @notice Gets the underlying amount for correlated token\n * @return underlyingAmount Amount of underlying token\n */\n function getUnderlyingAmount() public view virtual returns (uint256);\n\n /**\n * @notice Fetches price of the token based on an underlying exchange rate\n * @param exchangeRate The underlying exchange rate to use\n * @return price The price of the token in scaled decimal places\n */\n function _calculatePrice(uint256 exchangeRate) internal view returns (uint256) {\n uint256 underlyingUSDPrice = RESILIENT_ORACLE.getPrice(UNDERLYING_TOKEN);\n\n IERC20Metadata token = IERC20Metadata(CORRELATED_TOKEN);\n uint256 decimals = token.decimals();\n\n return (exchangeRate * underlyingUSDPrice) / (10 ** decimals);\n }\n\n /**\n * @notice Reverts if the call is not allowed by AccessControlManager\n * @param signature Method signature\n * @custom:error Unauthorized error is thrown if the call is not allowed\n */\n function _checkAccessAllowed(string memory signature) internal view {\n bool isAllowedToCall = ACCESS_CONTROL_MANAGER.isAllowedToCall(msg.sender, signature);\n\n if (!isAllowedToCall) {\n revert Unauthorized(msg.sender, address(this), signature);\n }\n }\n}\n" + }, + "contracts/oracles/ERC4626Oracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IERC4626 } from \"../interfaces/IERC4626.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title ERC4626Oracle\n * @author Venus\n * @notice This oracle fetches the price of ERC4626 tokens\n */\ncontract ERC4626Oracle is CorrelatedTokenOracle {\n uint256 public immutable ONE_CORRELATED_TOKEN;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ONE_CORRELATED_TOKEN = 10 ** IERC4626(correlatedToken).decimals();\n }\n\n /**\n * @notice Fetches the amount of underlying token for 1 correlated token\n * @return amount The amount of underlying token for correlated token\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IERC4626(CORRELATED_TOKEN).convertToAssets(ONE_CORRELATED_TOKEN);\n }\n}\n" + }, + "contracts/oracles/EtherfiAccountantOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IAccountant } from \"../interfaces/IAccountant.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title EtherfiAccountantOracle\n * @author Venus\n * @notice This oracle fetches the price of any Ether.fi asset that uses\n * Accountant contracts to derive the underlying price\n */\ncontract EtherfiAccountantOracle is CorrelatedTokenOracle {\n /// @notice Address of Accountant\n IAccountant public immutable ACCOUNTANT;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address accountant,\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(accountant);\n ACCOUNTANT = IAccountant(accountant);\n }\n\n /**\n * @notice Fetches the conversion rate from the ACCOUNTANT contract\n * @return amount Amount of WBTC\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ACCOUNTANT.getRateSafe();\n }\n}\n" + }, + "contracts/oracles/mocks/MockAccountant.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/IAccountant.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAccountant is IAccountant, Ownable {\n uint256 public rate;\n\n constructor() Ownable() {}\n\n function setRate(uint256 _rate) external onlyOwner {\n rate = _rate;\n }\n\n function getRateSafe() external view override returns (uint256) {\n return rate;\n }\n}\n" + }, + "contracts/oracles/mocks/MockBinanceFeedRegistry.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/FeedRegistryInterface.sol\";\n\ncontract MockBinanceFeedRegistry is FeedRegistryInterface {\n mapping(string => uint256) public assetPrices;\n\n function setAssetPrice(string memory base, uint256 price) external {\n assetPrices[base] = price;\n }\n\n function latestRoundDataByName(\n string memory base,\n string memory quote\n )\n external\n view\n override\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n quote;\n return (0, int256(assetPrices[base]), 0, block.timestamp - 10, 0);\n }\n\n function decimalsByName(string memory base, string memory quote) external view override returns (uint8) {\n return 8;\n }\n}\n" + }, + "contracts/oracles/mocks/MockBinanceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OwnableUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\nimport { OracleInterface } from \"../../interfaces/OracleInterface.sol\";\n\ncontract MockBinanceOracle is OwnableUpgradeable, OracleInterface {\n mapping(address => uint256) public assetPrices;\n\n constructor() {}\n\n function initialize() public initializer {}\n\n function setPrice(address asset, uint256 price) external {\n assetPrices[asset] = price;\n }\n\n function getPrice(address token) public view returns (uint256) {\n return assetPrices[token];\n }\n}\n" + }, + "contracts/oracles/mocks/MockChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OwnableUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\nimport { OracleInterface } from \"../../interfaces/OracleInterface.sol\";\n\ncontract MockChainlinkOracle is OwnableUpgradeable, OracleInterface {\n mapping(address => uint256) public assetPrices;\n\n //set price in 6 decimal precision\n constructor() {}\n\n function initialize() public initializer {\n __Ownable_init();\n }\n\n function setPrice(address asset, uint256 price) external {\n assetPrices[asset] = price;\n }\n\n //https://compound.finance/docs/prices\n function getPrice(address token) public view returns (uint256) {\n return assetPrices[token];\n }\n}\n" + }, + "contracts/oracles/mocks/MockPendlePtOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/IPendlePtOracle.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockPendlePtOracle is IPendlePtOracle, Ownable {\n mapping(address => mapping(uint32 => uint256)) public ptToAssetRate;\n mapping(address => mapping(uint32 => uint256)) public ptToSyRate;\n\n constructor() Ownable() {}\n\n function setPtToAssetRate(address market, uint32 duration, uint256 rate) external onlyOwner {\n ptToAssetRate[market][duration] = rate;\n }\n\n function setPtToSyRate(address market, uint32 duration, uint256 rate) external onlyOwner {\n ptToSyRate[market][duration] = rate;\n }\n\n function getPtToAssetRate(address market, uint32 duration) external view returns (uint256) {\n return ptToAssetRate[market][duration];\n }\n\n function getPtToSyRate(address market, uint32 duration) external view returns (uint256) {\n return ptToSyRate[market][duration];\n }\n\n function getOracleState(\n address /* market */,\n uint32 /* duration */\n )\n external\n pure\n returns (bool increaseCardinalityRequired, uint16 cardinalityRequired, bool oldestObservationSatisfied)\n {\n return (false, 0, true);\n }\n}\n" + }, + "contracts/oracles/mocks/MockSFrxEthFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/ISfrxEthFraxOracle.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockSfrxEthFraxOracle is ISfrxEthFraxOracle, Ownable {\n bool public isBadData;\n uint256 public priceLow;\n uint256 public priceHigh;\n\n constructor() Ownable() {}\n\n function setPrices(bool _isBadData, uint256 _priceLow, uint256 _priceHigh) external onlyOwner {\n isBadData = _isBadData;\n priceLow = _priceLow;\n priceHigh = _priceHigh;\n }\n\n function getPrices() external view override returns (bool, uint256, uint256) {\n return (isBadData, priceLow, priceHigh);\n }\n}\n" + }, + "contracts/oracles/OneJumpOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\n/**\n * @title OneJumpOracle\n * @author Venus\n * @notice This oracle fetches the price of an asset in through an intermediate asset\n */\ncontract OneJumpOracle is CorrelatedTokenOracle {\n /// @notice Address of the intermediate oracle\n OracleInterface public immutable INTERMEDIATE_ORACLE;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n address intermediateOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(intermediateOracle);\n INTERMEDIATE_ORACLE = OracleInterface(intermediateOracle);\n }\n\n /**\n * @notice Fetches the amount of the underlying token for 1 correlated token, using the intermediate oracle\n * @return amount The amount of the underlying token for 1 correlated token scaled by the underlying token decimals\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n uint256 underlyingDecimals = IERC20Metadata(UNDERLYING_TOKEN).decimals();\n uint256 correlatedDecimals = IERC20Metadata(CORRELATED_TOKEN).decimals();\n\n uint256 underlyingAmount = INTERMEDIATE_ORACLE.getPrice(CORRELATED_TOKEN);\n\n return (underlyingAmount * (10 ** correlatedDecimals)) / (10 ** (36 - underlyingDecimals));\n }\n}\n" + }, + "contracts/oracles/PendleOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IPendlePtOracle } from \"../interfaces/IPendlePtOracle.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\n/**\n * @title PendleOracle\n * @author Venus\n * @notice This oracle fetches the price of a pendle token\n * @dev As a base price the oracle uses either the price of the Pendle\n * market's asset (in this case PT_TO_ASSET rate should be used) or\n * the price of the Pendle market's interest bearing token (e.g. wstETH\n * for stETH; in this case PT_TO_SY rate should be used). Technically,\n * interest bearing token is different from standardized yield (SY) token,\n * but since SY is a wrapper around an interest bearing token, we can safely\n * assume the prices of the two are equal. This is not always true for asset\n * price though: using PT_TO_ASSET rate assumes that the yield token can\n * be seamlessly redeemed for the underlying asset. In reality, this might\n * not always be the case. For more details, see\n * https://docs.pendle.finance/Developers/Contracts/StandardizedYield\n */\ncontract PendleOracle is CorrelatedTokenOracle {\n struct ConstructorParams {\n /// @notice Pendle market\n address market;\n /// @notice Pendle oracle\n address ptOracle;\n /// @notice Either PT_TO_ASSET or PT_TO_SY\n RateKind rateKind;\n /// @notice Pendle PT token\n address ptToken;\n /// @notice Underlying token, can be either the market's asset or the interest bearing token\n address underlyingToken;\n /// @notice Resilient oracle to get the underlying token price from\n address resilientOracle;\n /// @notice TWAP duration to call Pendle oracle with\n uint32 twapDuration;\n /// @notice Annual growth rate of the underlying token\n uint256 annualGrowthRate;\n /// @notice Snapshot interval for the oracle\n uint256 snapshotInterval;\n /// @notice Initial exchange rate of the underlying token\n uint256 initialSnapshotMaxExchangeRate;\n /// @notice Initial timestamp of the underlying token\n uint256 initialSnapshotTimestamp;\n /// @notice Access control manager\n address accessControlManager;\n /// @notice Gap to add when updating the snapshot\n uint256 snapshotGap;\n }\n\n /// @notice Which asset to use as a base for the returned PT\n /// price. Can be either a standardized yield token (SY), in\n /// this case PT/SY price is returned, or the Pendle\n /// market's asset directly.\n enum RateKind {\n PT_TO_ASSET,\n PT_TO_SY\n }\n\n /// @notice Address of the PT oracle\n IPendlePtOracle public immutable PT_ORACLE;\n\n /// @notice Whether to use PT/SY (standardized yield token) rate\n /// or PT/market asset rate\n RateKind public immutable RATE_KIND;\n\n /// @notice Address of the market\n address public immutable MARKET;\n\n /// @notice Twap duration for the oracle\n uint32 public immutable TWAP_DURATION;\n\n /// @notice Decimals of the underlying token\n /// @dev We make an assumption that the underlying decimals will\n /// not change throughout the lifetime of the Pendle market\n uint8 public immutable UNDERLYING_DECIMALS;\n\n /// @notice Thrown if the duration is invalid\n error InvalidDuration();\n\n /**\n * @notice Constructor for the implementation contract.\n * @custom:error InvalidDuration Thrown if the duration is invalid\n */\n constructor(\n ConstructorParams memory params\n )\n CorrelatedTokenOracle(\n params.ptToken,\n params.underlyingToken,\n params.resilientOracle,\n params.annualGrowthRate,\n params.snapshotInterval,\n params.initialSnapshotMaxExchangeRate,\n params.initialSnapshotTimestamp,\n params.accessControlManager,\n params.snapshotGap\n )\n {\n ensureNonzeroAddress(params.market);\n ensureNonzeroAddress(params.ptOracle);\n ensureNonzeroValue(params.twapDuration);\n\n MARKET = params.market;\n PT_ORACLE = IPendlePtOracle(params.ptOracle);\n RATE_KIND = params.rateKind;\n TWAP_DURATION = params.twapDuration;\n UNDERLYING_DECIMALS = IERC20Metadata(UNDERLYING_TOKEN).decimals();\n\n (bool increaseCardinalityRequired, , bool oldestObservationSatisfied) = PT_ORACLE.getOracleState(\n MARKET,\n TWAP_DURATION\n );\n if (increaseCardinalityRequired || !oldestObservationSatisfied) {\n revert InvalidDuration();\n }\n }\n\n /// @notice Fetches the amount of underlying token for 1 PT\n /// @return amount The amount of underlying token (either the market's asset\n /// or the yield token) for 1 PT, adjusted for decimals such that the result\n /// has the same precision as the underlying token\n function getUnderlyingAmount() public view override returns (uint256) {\n uint256 rate;\n if (RATE_KIND == RateKind.PT_TO_SY) {\n rate = PT_ORACLE.getPtToSyRate(MARKET, TWAP_DURATION);\n } else {\n rate = PT_ORACLE.getPtToAssetRate(MARKET, TWAP_DURATION);\n }\n return ((10 ** UNDERLYING_DECIMALS) * rate) / 1e18;\n }\n}\n" + }, + "contracts/oracles/SequencerChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.25;\n\nimport { ChainlinkOracle } from \"./ChainlinkOracle.sol\";\nimport { AggregatorV3Interface } from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\n\n/**\n @title Sequencer Chain Link Oracle\n @notice Oracle to fetch price using chainlink oracles on L2s with sequencer\n*/\ncontract SequencerChainlinkOracle is ChainlinkOracle {\n /// @notice L2 Sequencer feed\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n AggregatorV3Interface public immutable sequencer;\n\n /// @notice L2 Sequencer grace period\n uint256 public constant GRACE_PERIOD_TIME = 3600;\n\n /**\n @notice Contract constructor\n @param _sequencer L2 sequencer\n @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(AggregatorV3Interface _sequencer) ChainlinkOracle() {\n require(address(_sequencer) != address(0), \"zero address\");\n\n sequencer = _sequencer;\n }\n\n /// @inheritdoc ChainlinkOracle\n function getPrice(address asset) public view override returns (uint) {\n if (!isSequencerActive()) revert(\"L2 sequencer unavailable\");\n return super.getPrice(asset);\n }\n\n function isSequencerActive() internal view returns (bool) {\n // answer from oracle is a variable with a value of either 1 or 0\n // 0: The sequencer is up\n // 1: The sequencer is down\n // startedAt: This timestamp indicates when the sequencer changed status\n (, int256 answer, uint256 startedAt, , ) = sequencer.latestRoundData();\n if (block.timestamp - startedAt <= GRACE_PERIOD_TIME || answer == 1) return false;\n return true;\n }\n}\n" + }, + "contracts/oracles/SFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISFrax } from \"../interfaces/ISFrax.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title SFraxOracle\n * @author Venus\n * @notice This oracle fetches the price of sFrax\n */\ncontract SFraxOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address sFrax,\n address frax,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n sFrax,\n frax,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of FRAX for 1 sFrax\n * @return amount The amount of FRAX for sFrax\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ISFrax(CORRELATED_TOKEN).convertToAssets(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/SFrxETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISfrxEthFraxOracle } from \"../interfaces/ISfrxEthFraxOracle.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title SFrxETHOracle\n * @author Venus\n * @notice This oracle fetches the price of sfrxETH\n */\ncontract SFrxETHOracle is AccessControlledV8, OracleInterface {\n /// @notice Address of SfrxEthFraxOracle\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ISfrxEthFraxOracle public immutable SFRXETH_FRAX_ORACLE;\n\n /// @notice Address of sfrxETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable SFRXETH;\n\n /// @notice Maximum allowed price difference\n uint256 public maxAllowedPriceDifference;\n\n /// @notice Emits when the maximum allowed price difference is updated\n event MaxAllowedPriceDifferenceUpdated(uint256 oldMaxAllowedPriceDifference, uint256 newMaxAllowedPriceDifference);\n\n /// @notice Thrown if the price data is invalid\n error BadPriceData();\n\n /// @notice Thrown if the price difference exceeds the allowed limit\n error PriceDifferenceExceeded();\n\n /// @notice Thrown if the token address is invalid\n error InvalidTokenAddress();\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n /// @custom:error ZeroAddressNotAllowed is thrown when `_sfrxEthFraxOracle` or `_sfrxETH` are the zero address\n constructor(address _sfrxEthFraxOracle, address _sfrxETH) {\n ensureNonzeroAddress(_sfrxEthFraxOracle);\n ensureNonzeroAddress(_sfrxETH);\n\n SFRXETH_FRAX_ORACLE = ISfrxEthFraxOracle(_sfrxEthFraxOracle);\n SFRXETH = _sfrxETH;\n\n _disableInitializers();\n }\n\n /**\n * @notice Sets the contracts required to fetch prices\n * @param _acm Address of the access control manager contract\n * @param _maxAllowedPriceDifference Maximum allowed price difference\n * @custom:error ZeroValueNotAllowed is thrown if `_maxAllowedPriceDifference` is zero\n */\n function initialize(address _acm, uint256 _maxAllowedPriceDifference) external initializer {\n ensureNonzeroValue(_maxAllowedPriceDifference);\n\n __AccessControlled_init(_acm);\n maxAllowedPriceDifference = _maxAllowedPriceDifference;\n }\n\n /**\n * @notice Sets the maximum allowed price difference\n * @param _maxAllowedPriceDifference Maximum allowed price difference\n * @custom:error ZeroValueNotAllowed is thrown if `_maxAllowedPriceDifference` is zero\n */\n function setMaxAllowedPriceDifference(uint256 _maxAllowedPriceDifference) external {\n _checkAccessAllowed(\"setMaxAllowedPriceDifference(uint256)\");\n ensureNonzeroValue(_maxAllowedPriceDifference);\n\n emit MaxAllowedPriceDifferenceUpdated(maxAllowedPriceDifference, _maxAllowedPriceDifference);\n maxAllowedPriceDifference = _maxAllowedPriceDifference;\n }\n\n /**\n * @notice Fetches the USD price of sfrxETH\n * @param asset Address of the sfrxETH token\n * @return price The price scaled by 1e18\n * @custom:error InvalidTokenAddress is thrown when the `asset` is not the sfrxETH token (`SFRXETH`)\n * @custom:error BadPriceData is thrown if the `SFRXETH_FRAX_ORACLE` oracle informs it has bad data\n * @custom:error ZeroValueNotAllowed is thrown if the prices (low or high, in USD) are zero\n * @custom:error PriceDifferenceExceeded is thrown if priceHigh/priceLow is greater than `maxAllowedPriceDifference`\n */\n function getPrice(address asset) external view returns (uint256) {\n if (asset != SFRXETH) revert InvalidTokenAddress();\n\n (bool isBadData, uint256 priceLow, uint256 priceHigh) = SFRXETH_FRAX_ORACLE.getPrices();\n\n if (isBadData) revert BadPriceData();\n\n // calculate price in USD\n uint256 priceHighInUSD = (EXP_SCALE ** 2) / priceLow;\n uint256 priceLowInUSD = (EXP_SCALE ** 2) / priceHigh;\n\n ensureNonzeroValue(priceHighInUSD);\n ensureNonzeroValue(priceLowInUSD);\n\n // validate price difference\n uint256 difference = (priceHighInUSD * EXP_SCALE) / priceLowInUSD;\n if (difference > maxAllowedPriceDifference) revert PriceDifferenceExceeded();\n\n // calculate and return average price\n return (priceHighInUSD + priceLowInUSD) / 2;\n }\n}\n" + }, + "contracts/oracles/SlisBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISynclubStakeManager } from \"../interfaces/ISynclubStakeManager.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title SlisBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of slisBNB asset\n */\ncontract SlisBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakeManager\n ISynclubStakeManager public immutable STAKE_MANAGER;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakeManager,\n address slisBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n slisBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakeManager);\n STAKE_MANAGER = ISynclubStakeManager(stakeManager);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 slisBNB\n * @return amount The amount of BNB for slisBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STAKE_MANAGER.convertSnBnbToBnb(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/StableUsdtPriceFeed.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ResilientOracleInterface } from \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title StableUsdtPriceFeed\n * @dev This contract is used to get the price of USDT from a Resilient Oracle\n * and bounds the price to a certain range.\n */\ncontract StableUsdtPriceFeed {\n ResilientOracleInterface public resilientOracle;\n\n address public constant USDT_TOKEN_ADDR = 0x55d398326f99059fF775485246999027B3197955;\n uint256 public constant UPPER_BOUND = 1020000000000000000; // 1.02 USD\n uint256 public constant LOWER_BOUND = 980000000000000000; // 0.98 USD\n\n constructor(address _resilientOracle) {\n require(_resilientOracle != address(0), \"Zero address provided\");\n resilientOracle = ResilientOracleInterface(_resilientOracle);\n }\n\n function latestAnswer() external view returns (int256 answer) {\n // get price\n uint256 price = getPrice();\n // cast price to int256\n answer = int256(price);\n }\n\n function latestRoundData()\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n // get price\n uint256 _answer = getPrice();\n // mock timestamp to latest block timestamp\n uint256 timestamp = block.timestamp;\n // mock roundId to timestamp\n roundId = uint80(timestamp);\n return (roundId, int256(_answer), timestamp, timestamp, roundId);\n }\n\n function decimals() external pure returns (uint8) {\n return 18;\n }\n\n function description() external pure returns (string memory) {\n return \"Stabilized USDT Price Feed\";\n }\n\n function version() external pure returns (uint256) {\n return 1;\n }\n\n /**\n * @dev Get the price from the Resilient Oracle, and bound it to the range\n * @return price The price of USDT in 18 decimals\n */\n function getPrice() private view returns (uint256 price) {\n // get USDT price (18 decimals)\n price = resilientOracle.getPrice(USDT_TOKEN_ADDR);\n price = price < LOWER_BOUND ? LOWER_BOUND : (price > UPPER_BOUND ? UPPER_BOUND : price);\n }\n}\n" + }, + "contracts/oracles/StkBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IPStakePool } from \"../interfaces/IPStakePool.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title StkBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of stkBNB asset\n */\ncontract StkBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakePool\n IPStakePool public immutable STAKE_POOL;\n\n /// @notice Thrown if the pool token supply is zero\n error PoolTokenSupplyIsZero();\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakePool,\n address stkBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n stkBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakePool);\n STAKE_POOL = IPStakePool(stakePool);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 stkBNB\n * @return price The amount of BNB for stkBNB\n * @custom:error PoolTokenSupplyIsZero error is thrown if the pool token supply is zero\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n IPStakePool.Data memory exchangeRateData = STAKE_POOL.exchangeRate();\n\n if (exchangeRateData.poolTokenSupply == 0) {\n revert PoolTokenSupplyIsZero();\n }\n\n return (exchangeRateData.totalWei * EXP_SCALE) / exchangeRateData.poolTokenSupply;\n }\n}\n" + }, + "contracts/oracles/WBETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IWBETH } from \"../interfaces/IWBETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title WBETHOracle\n * @author Venus\n * @notice This oracle fetches the price of wBETH asset\n */\ncontract WBETHOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address wbeth,\n address eth,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n wbeth,\n eth,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of ETH for 1 wBETH\n * @return amount The amount of ETH for wBETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IWBETH(CORRELATED_TOKEN).exchangeRate();\n }\n}\n" + }, + "contracts/oracles/WeETHAccountantOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IAccountant } from \"../interfaces/IAccountant.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WeETHAccountantOracle\n * @author Venus\n * @notice This oracle fetches the price of Ether.fi tokens based on an `Accountant` contract (i.e. weETHs and weETHk)\n */\ncontract WeETHAccountantOracle is CorrelatedTokenOracle {\n /// @notice Address of Accountant\n IAccountant public immutable ACCOUNTANT;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address accountant,\n address weethLRT,\n address weth,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n weethLRT,\n weth,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(accountant);\n ACCOUNTANT = IAccountant(accountant);\n }\n\n /**\n * @notice Gets the WETH for 1 weETH LRT\n * @return amount Amount of WETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ACCOUNTANT.getRateSafe();\n }\n}\n" + }, + "contracts/oracles/WeETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IEtherFiLiquidityPool } from \"../interfaces/IEtherFiLiquidityPool.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WeETHOracle\n * @author Venus\n * @notice This oracle fetches the price of weETH\n */\ncontract WeETHOracle is CorrelatedTokenOracle {\n /// @notice Address of Liqiudity pool\n IEtherFiLiquidityPool public immutable LIQUIDITY_POOL;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address liquidityPool,\n address weETH,\n address eETH,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n weETH,\n eETH,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(liquidityPool);\n LIQUIDITY_POOL = IEtherFiLiquidityPool(liquidityPool);\n }\n\n /**\n * @notice Gets the eETH for 1 weETH\n * @return amount Amount of eETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return LIQUIDITY_POOL.amountForShare(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/WstETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\nimport { IStETH } from \"../interfaces/IStETH.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title WstETHOracle\n * @author Venus\n * @notice Depending on the equivalence flag price is either based on assumption that 1 stETH = 1 ETH\n * or the price of stETH/USD (secondary market price) is obtained from the oracle.\n */\ncontract WstETHOracle is OracleInterface {\n /// @notice A flag assuming 1:1 price equivalence between stETH/ETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n bool public immutable ASSUME_STETH_ETH_EQUIVALENCE;\n\n /// @notice Address of stETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IStETH public immutable STETH;\n\n /// @notice Address of wstETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable WSTETH_ADDRESS;\n\n /// @notice Address of WETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable WETH_ADDRESS;\n\n /// @notice Address of Resilient Oracle\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n OracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(\n address wstETHAddress,\n address wETHAddress,\n address stETHAddress,\n address resilientOracleAddress,\n bool assumeEquivalence\n ) {\n ensureNonzeroAddress(wstETHAddress);\n ensureNonzeroAddress(wETHAddress);\n ensureNonzeroAddress(stETHAddress);\n ensureNonzeroAddress(resilientOracleAddress);\n WSTETH_ADDRESS = wstETHAddress;\n WETH_ADDRESS = wETHAddress;\n STETH = IStETH(stETHAddress);\n RESILIENT_ORACLE = OracleInterface(resilientOracleAddress);\n ASSUME_STETH_ETH_EQUIVALENCE = assumeEquivalence;\n }\n\n /**\n * @notice Gets the USD price of wstETH asset\n * @dev Depending on the equivalence flag price is either based on assumption that 1 stETH = 1 ETH\n * or the price of stETH/USD (secondary market price) is obtained from the oracle\n * @param asset Address of wstETH\n * @return wstETH Price in USD scaled by 1e18\n */\n function getPrice(address asset) public view returns (uint256) {\n if (asset != WSTETH_ADDRESS) revert(\"wrong wstETH address\");\n\n // get stETH amount for 1 wstETH scaled by 1e18\n uint256 stETHAmount = STETH.getPooledEthByShares(1 ether);\n\n // price is scaled 1e18 (oracle returns 36 - asset decimal scale)\n uint256 stETHUSDPrice = RESILIENT_ORACLE.getPrice(ASSUME_STETH_ETH_EQUIVALENCE ? WETH_ADDRESS : address(STETH));\n\n // stETHAmount (for 1 wstETH) * stETHUSDPrice / 1e18\n return (stETHAmount * stETHUSDPrice) / EXP_SCALE;\n }\n}\n" + }, + "contracts/oracles/WstETHOracleV2.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IStETH } from \"../interfaces/IStETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WstETHOracleV2\n * @author Venus\n * @notice This oracle fetches the price of wstETH\n */\ncontract WstETHOracleV2 is CorrelatedTokenOracle {\n /// @notice Address of stETH\n IStETH public immutable STETH;\n\n /// @notice Constructor for the implementation contract.\n /// @dev The underlyingToken must be correlated so that 1 underlyingToken is equal to 1 stETH, because\n /// getUnderlyingAmount() implicitly assumes that\n constructor(\n address stETH,\n address wstETH,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n wstETH,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stETH);\n STETH = IStETH(stETH);\n }\n\n /**\n * @notice Gets the amount of underlyingToken for 1 wstETH, assuming that 1 underlyingToken is equivalent to 1 stETH\n * @return amount Amount of underlyingToken\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STETH.getPooledEthByShares(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/ZkETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IZkETH } from \"../interfaces/IZkETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title ZkETHOracle\n * @author Venus\n * @notice This oracle fetches the price of zkETH\n */\ncontract ZkETHOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address zkETH,\n address rzkETH,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n zkETH,\n rzkETH,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Gets the amount of rzkETH for 1 zkETH\n * @return amount Amount of rzkETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IZkETH(CORRELATED_TOKEN).LSTPerToken();\n }\n}\n" + }, + "contracts/ReferenceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2025 Venus\npragma solidity 0.8.25;\n\nimport { Ownable2StepUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { ResilientOracleInterface, OracleInterface } from \"./interfaces/OracleInterface.sol\";\n\n/**\n * @title ReferenceOracle\n * @author Venus\n * @notice Reference oracle is the oracle that is not used for production but required for\n * price monitoring. This oracle contains some extra configurations for assets required to\n * compute reference prices of their derivative assets (OneJump, ERC4626, Pendle, etc.)\n */\ncontract ReferenceOracle is Ownable2StepUpgradeable, OracleInterface {\n struct ExternalPrice {\n /// @notice asset address\n address asset;\n /// @notice price of the asset from an external source\n uint256 price;\n }\n\n /// @notice Slot to temporarily store price information from external sources\n /// like CMC/Coingecko, useful to compute prices of derivative assets based on\n /// prices of the base assets with no on chain price information\n bytes32 public constant PRICES_SLOT = keccak256(abi.encode(\"venus-protocol/oracle/ReferenceOracle/prices\"));\n\n /// @notice Resilient oracle address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Oracle configuration for assets\n mapping(address => OracleInterface) public oracles;\n\n /// @notice Event emitted when an oracle is set\n event OracleConfigured(address indexed asset, address indexed oracle);\n\n /**\n * @notice Constructor for the implementation contract. Sets immutable variables.\n * @param resilientOracle Resilient oracle address\n * @custom:error ZeroAddressNotAllowed is thrown if resilient oracle address is null\n * @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(ResilientOracleInterface resilientOracle) {\n ensureNonzeroAddress(address(resilientOracle));\n RESILIENT_ORACLE = resilientOracle;\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin\n */\n function initialize() external initializer {\n __Ownable2Step_init();\n }\n\n /**\n * @notice Sets an oracle to use for a specific asset\n * @dev The production resilientOracle will be used if zero address is passed\n * @param asset Asset address\n * @param oracle Oracle address\n * @custom:access Only owner\n * @custom:error ZeroAddressNotAllowed is thrown if asset address is null\n * @custom:event Emits OracleConfigured event\n */\n function setOracle(address asset, OracleInterface oracle) external onlyOwner {\n ensureNonzeroAddress(asset);\n oracles[asset] = OracleInterface(oracle);\n emit OracleConfigured(asset, address(oracle));\n }\n\n /**\n * @notice Gets price of the asset assuming other assets have the defined price\n * @param asset asset address\n * @param externalPrices an array of prices for other assets\n * @return USD price in scaled decimal places\n */\n function getPriceAssuming(address asset, ExternalPrice[] memory externalPrices) external returns (uint256) {\n uint256 externalPricesCount = externalPrices.length;\n for (uint256 i = 0; i < externalPricesCount; ++i) {\n _storeExternalPrice(externalPrices[i].asset, externalPrices[i].price);\n }\n return _getPrice(asset);\n }\n\n /**\n * @notice Gets price of the asset\n * @param asset asset address\n * @return USD price in scaled decimal places\n */\n function getPrice(address asset) external view override returns (uint256) {\n return _getPrice(asset);\n }\n\n function _storeExternalPrice(address asset, uint256 price) internal {\n bytes32 slot = keccak256(abi.encode(PRICES_SLOT, asset));\n // solhint-disable-next-line no-inline-assembly\n assembly (\"memory-safe\") {\n tstore(slot, price)\n }\n }\n\n function _getPrice(address asset) internal view returns (uint256) {\n uint256 externalPrice = _loadExternalPrice(asset);\n if (externalPrice != 0) {\n return externalPrice;\n }\n OracleInterface oracle = oracles[asset];\n if (oracle != OracleInterface(address(0))) {\n return oracle.getPrice(asset);\n }\n return RESILIENT_ORACLE.getPrice(asset);\n }\n\n function _loadExternalPrice(address asset) internal view returns (uint256 value) {\n bytes32 slot = keccak256(abi.encode(PRICES_SLOT, asset));\n // solhint-disable-next-line no-inline-assembly\n assembly (\"memory-safe\") {\n value := tload(slot)\n }\n }\n}\n" + }, + "contracts/ResilientOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity 0.8.25;\n\nimport { PausableUpgradeable } from \"@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol\";\nimport { VBep20Interface } from \"./interfaces/VBep20Interface.sol\";\nimport { OracleInterface, ResilientOracleInterface, BoundValidatorInterface } from \"./interfaces/OracleInterface.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { ICappedOracle } from \"./interfaces/ICappedOracle.sol\";\nimport { Transient } from \"./lib/Transient.sol\";\n\n/**\n * @title ResilientOracle\n * @author Venus\n * @notice The Resilient Oracle is the main contract that the protocol uses to fetch prices of assets.\n *\n * DeFi protocols are vulnerable to price oracle failures including oracle manipulation and incorrectly\n * reported prices. If only one oracle is used, this creates a single point of failure and opens a vector\n * for attacking the protocol.\n *\n * The Resilient Oracle uses multiple sources and fallback mechanisms to provide accurate prices and protect\n * the protocol from oracle attacks.\n *\n * For every market (vToken) we configure the main, pivot and fallback oracles. The oracles are configured per\n * vToken's underlying asset address. The main oracle oracle is the most trustworthy price source, the pivot\n * oracle is used as a loose sanity checker and the fallback oracle is used as a backup price source.\n *\n * To validate prices returned from two oracles, we use an upper and lower bound ratio that is set for every\n * market. The upper bound ratio represents the deviation between reported price (the price that’s being\n * validated) and the anchor price (the price we are validating against) above which the reported price will\n * be invalidated. The lower bound ratio presents the deviation between reported price and anchor price below\n * which the reported price will be invalidated. So for oracle price to be considered valid the below statement\n * should be true:\n\n```\nanchorRatio = anchorPrice/reporterPrice\nisValid = anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio\n```\n\n * In most cases, Chainlink is used as the main oracle, other oracles are used as the pivot oracle depending\n * on which supports the given market and Binance oracle is used as the fallback oracle.\n *\n * For a fetched price to be valid it must be positive and not stagnant. If the price is invalid then we consider the\n * oracle to be stagnant and treat it like it's disabled.\n */\ncontract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOracleInterface {\n /**\n * @dev Oracle roles:\n * **main**: The most trustworthy price source\n * **pivot**: Price oracle used as a loose sanity checker\n * **fallback**: The backup source when main oracle price is invalidated\n */\n enum OracleRole {\n MAIN,\n PIVOT,\n FALLBACK\n }\n\n struct TokenConfig {\n /// @notice asset address\n address asset;\n /// @notice `oracles` stores the oracles based on their role in the following order:\n /// [main, pivot, fallback],\n /// It can be indexed with the corresponding enum OracleRole value\n address[3] oracles;\n /// @notice `enableFlagsForOracles` stores the enabled state\n /// for each oracle in the same order as `oracles`\n bool[3] enableFlagsForOracles;\n /// @notice `cachingEnabled` is a flag that indicates whether the asset price should be cached\n bool cachingEnabled;\n }\n\n uint256 public constant INVALID_PRICE = 0;\n\n /// @notice Native market address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable nativeMarket;\n\n /// @notice VAI address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vai;\n\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\n /// and can serve as any underlying asset of a market that supports native tokens\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Slot to cache the asset's price, used for transient storage\n /// custom:storage-location erc7201:venus-protocol/oracle/ResilientOracle/cache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/ResilientOracle/cache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant CACHE_SLOT = 0x4e99ec55972332f5e0ef9c6623192c0401b609161bffae64d9ccdd7ad6cc7800;\n\n /// @notice Bound validator contract address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n BoundValidatorInterface public immutable boundValidator;\n\n mapping(address => TokenConfig) private tokenConfigs;\n\n event TokenConfigAdded(\n address indexed asset,\n address indexed mainOracle,\n address indexed pivotOracle,\n address fallbackOracle\n );\n\n /// Event emitted when an oracle is set\n event OracleSet(address indexed asset, address indexed oracle, uint256 indexed role);\n\n /// Event emitted when an oracle is enabled or disabled\n event OracleEnabled(address indexed asset, uint256 indexed role, bool indexed enable);\n\n /// Event emitted when an asset cachingEnabled flag is set\n event CachedEnabled(address indexed asset, bool indexed enabled);\n\n /**\n * @notice Checks whether an address is null or not\n */\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /**\n * @notice Checks whether token config exists by checking whether asset is null address\n * @dev address can't be null, so it's suitable to be used to check the validity of the config\n * @param asset asset address\n */\n modifier checkTokenConfigExistence(address asset) {\n if (tokenConfigs[asset].asset == address(0)) revert(\"token config must exist\");\n _;\n }\n\n /// @notice Constructor for the implementation contract. Sets immutable variables.\n /// @dev nativeMarketAddress can be address(0) if on the chain we do not support native market\n /// (e.g vETH on ethereum would not be supported, only vWETH)\n /// @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\n /// @param vaiAddress The address of the VAI token (if there is VAI on the deployed chain).\n /// Set to address(0) of VAI is not existent.\n /// @param _boundValidator Address of the bound validator contract\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(\n address nativeMarketAddress,\n address vaiAddress,\n BoundValidatorInterface _boundValidator\n ) notNullAddress(address(_boundValidator)) {\n nativeMarket = nativeMarketAddress;\n vai = vaiAddress;\n boundValidator = _boundValidator;\n\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin and sets the BoundValidator contract address\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n __Pausable_init();\n }\n\n /**\n * @notice Pauses oracle\n * @custom:access Only Governance\n */\n function pause() external {\n _checkAccessAllowed(\"pause()\");\n _pause();\n }\n\n /**\n * @notice Unpauses oracle\n * @custom:access Only Governance\n */\n function unpause() external {\n _checkAccessAllowed(\"unpause()\");\n _unpause();\n }\n\n /**\n * @notice Batch sets token configs\n * @param tokenConfigs_ Token config array\n * @custom:access Only Governance\n * @custom:error Throws a length error if the length of the token configs array is 0\n */\n function setTokenConfigs(TokenConfig[] memory tokenConfigs_) external {\n if (tokenConfigs_.length == 0) revert(\"length can't be 0\");\n uint256 numTokenConfigs = tokenConfigs_.length;\n for (uint256 i; i < numTokenConfigs; ++i) {\n setTokenConfig(tokenConfigs_[i]);\n }\n }\n\n /**\n * @notice Sets oracle for a given asset and role.\n * @dev Supplied asset **must** exist and main oracle may not be null\n * @param asset Asset address\n * @param oracle Oracle address\n * @param role Oracle role\n * @custom:access Only Governance\n * @custom:error Null address error if main-role oracle address is null\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error TokenConfigExistance error is thrown if token config is not set\n * @custom:event Emits OracleSet event with asset address, oracle address and role of the oracle for the asset\n */\n function setOracle(\n address asset,\n address oracle,\n OracleRole role\n ) external notNullAddress(asset) checkTokenConfigExistence(asset) {\n _checkAccessAllowed(\"setOracle(address,address,uint8)\");\n if (oracle == address(0) && role == OracleRole.MAIN) revert(\"can't set zero address to main oracle\");\n tokenConfigs[asset].oracles[uint256(role)] = oracle;\n emit OracleSet(asset, oracle, uint256(role));\n }\n\n /**\n * @notice Enables/ disables oracle for the input asset. Token config for the input asset **must** exist\n * @dev Configuration for the asset **must** already exist and the asset cannot be 0 address\n * @param asset Asset address\n * @param role Oracle role\n * @param enable Enabled boolean of the oracle\n * @custom:access Only Governance\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error TokenConfigExistance error is thrown if token config is not set\n * @custom:event Emits OracleEnabled event with asset address, role of the oracle and enabled flag\n */\n function enableOracle(\n address asset,\n OracleRole role,\n bool enable\n ) external notNullAddress(asset) checkTokenConfigExistence(asset) {\n _checkAccessAllowed(\"enableOracle(address,uint8,bool)\");\n tokenConfigs[asset].enableFlagsForOracles[uint256(role)] = enable;\n emit OracleEnabled(asset, uint256(role), enable);\n }\n\n /**\n * @notice Updates the capped main oracle snapshot.\n * @dev This function should always be called before calling getUnderlyingPrice\n * @param vToken vToken address\n */\n function updatePrice(address vToken) external override {\n address asset = _getUnderlyingAsset(vToken);\n _updateAssetPrice(asset);\n }\n\n /**\n * @notice Updates the capped main oracle snapshot.\n * @dev This function should always be called before calling getPrice\n * @param asset asset address\n */\n function updateAssetPrice(address asset) external {\n _updateAssetPrice(asset);\n }\n\n /**\n * @dev Gets token config by asset address\n * @param asset asset address\n * @return tokenConfig Config for the asset\n */\n function getTokenConfig(address asset) external view returns (TokenConfig memory) {\n return tokenConfigs[asset];\n }\n\n /**\n * @notice Gets price of the underlying asset for a given vToken. Validation flow:\n * - Check if the oracle is paused globally\n * - Validate price from main oracle against pivot oracle\n * - Validate price from fallback oracle against pivot oracle if the first validation failed\n * - Validate price from main oracle against fallback oracle if the second validation failed\n * In the case that the pivot oracle is not available but main price is available and validation is successful,\n * main oracle price is returned.\n * @param vToken vToken address\n * @return price USD price in scaled decimal places.\n * @custom:error Paused error is thrown when resilent oracle is paused\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function getUnderlyingPrice(address vToken) external view override returns (uint256) {\n if (paused()) revert(\"resilient oracle is paused\");\n\n address asset = _getUnderlyingAsset(vToken);\n return _getPrice(asset);\n }\n\n /**\n * @notice Gets price of the asset\n * @param asset asset address\n * @return price USD price in scaled decimal places.\n * @custom:error Paused error is thrown when resilent oracle is paused\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function getPrice(address asset) external view override returns (uint256) {\n if (paused()) revert(\"resilient oracle is paused\");\n return _getPrice(asset);\n }\n\n /**\n * @notice Sets/resets single token configs.\n * @dev main oracle **must not** be a null address\n * @param tokenConfig Token config struct\n * @custom:access Only Governance\n * @custom:error NotNullAddress is thrown if asset address is null\n * @custom:error NotNullAddress is thrown if main-role oracle address for asset is null\n * @custom:event Emits TokenConfigAdded event when the asset config is set successfully by the authorized account\n * @custom:event Emits CachedEnabled event when the asset cachingEnabled flag is set successfully\n */\n function setTokenConfig(\n TokenConfig memory tokenConfig\n ) public notNullAddress(tokenConfig.asset) notNullAddress(tokenConfig.oracles[uint256(OracleRole.MAIN)]) {\n _checkAccessAllowed(\"setTokenConfig(TokenConfig)\");\n\n tokenConfigs[tokenConfig.asset] = tokenConfig;\n emit TokenConfigAdded(\n tokenConfig.asset,\n tokenConfig.oracles[uint256(OracleRole.MAIN)],\n tokenConfig.oracles[uint256(OracleRole.PIVOT)],\n tokenConfig.oracles[uint256(OracleRole.FALLBACK)]\n );\n emit CachedEnabled(tokenConfig.asset, tokenConfig.cachingEnabled);\n }\n\n /**\n * @notice Gets oracle and enabled status by asset address\n * @param asset asset address\n * @param role Oracle role\n * @return oracle Oracle address based on role\n * @return enabled Enabled flag of the oracle based on token config\n */\n function getOracle(address asset, OracleRole role) public view returns (address oracle, bool enabled) {\n oracle = tokenConfigs[asset].oracles[uint256(role)];\n enabled = tokenConfigs[asset].enableFlagsForOracles[uint256(role)];\n }\n\n /**\n * @notice Updates the capped oracle snapshot.\n * @dev Cache the asset price and return if already cached\n * @param asset asset address\n */\n function _updateAssetPrice(address asset) internal {\n if (Transient.readCachedPrice(CACHE_SLOT, asset) != 0) {\n return;\n }\n\n (address mainOracle, bool mainOracleEnabled) = getOracle(asset, OracleRole.MAIN);\n if (mainOracle != address(0) && mainOracleEnabled) {\n // if main oracle is not CorrelatedTokenOracle it will revert so we need to catch the revert\n try ICappedOracle(mainOracle).updateSnapshot() {} catch {}\n }\n\n if (_isCacheEnabled(asset)) {\n uint256 price = _getPrice(asset);\n Transient.cachePrice(CACHE_SLOT, asset, price);\n }\n }\n\n /**\n * @notice Gets price for the provided asset\n * @param asset asset address\n * @return price USD price in scaled decimal places.\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function _getPrice(address asset) internal view returns (uint256) {\n uint256 pivotPrice = INVALID_PRICE;\n uint256 price;\n\n price = Transient.readCachedPrice(CACHE_SLOT, asset);\n if (price != 0) {\n return price;\n }\n\n // Get pivot oracle price, Invalid price if not available or error\n (address pivotOracle, bool pivotOracleEnabled) = getOracle(asset, OracleRole.PIVOT);\n if (pivotOracleEnabled && pivotOracle != address(0)) {\n try OracleInterface(pivotOracle).getPrice(asset) returns (uint256 pricePivot) {\n pivotPrice = pricePivot;\n } catch {}\n }\n\n // Compare main price and pivot price, return main price and if validation was successful\n // note: In case pivot oracle is not available but main price is available and\n // validation is successful, the main oracle price is returned.\n (uint256 mainPrice, bool validatedPivotMain) = _getMainOraclePrice(\n asset,\n pivotPrice,\n pivotOracleEnabled && pivotOracle != address(0)\n );\n if (mainPrice != INVALID_PRICE && validatedPivotMain) return mainPrice;\n\n // Compare fallback and pivot if main oracle comparision fails with pivot\n // Return fallback price when fallback price is validated successfully with pivot oracle\n (uint256 fallbackPrice, bool validatedPivotFallback) = _getFallbackOraclePrice(asset, pivotPrice);\n if (fallbackPrice != INVALID_PRICE && validatedPivotFallback) return fallbackPrice;\n\n // Lastly compare main price and fallback price\n if (\n mainPrice != INVALID_PRICE &&\n fallbackPrice != INVALID_PRICE &&\n boundValidator.validatePriceWithAnchorPrice(asset, mainPrice, fallbackPrice)\n ) {\n return mainPrice;\n }\n\n revert(\"invalid resilient oracle price\");\n }\n\n /**\n * @notice Gets a price for the provided asset\n * @dev This function won't revert when price is 0, because the fallback oracle may still be\n * able to fetch a correct price\n * @param asset asset address\n * @param pivotPrice Pivot oracle price\n * @param pivotEnabled If pivot oracle is not empty and enabled\n * @return price USD price in scaled decimals\n * e.g. asset decimals is 8 then price is returned as 10**18 * 10**(18-8) = 10**28 decimals\n * @return pivotValidated Boolean representing if the validation of main oracle price\n * and pivot oracle price were successful\n * @custom:error Invalid price error is thrown if main oracle fails to fetch price of the asset\n * @custom:error Invalid price error is thrown if main oracle is not enabled or main oracle\n * address is null\n */\n function _getMainOraclePrice(\n address asset,\n uint256 pivotPrice,\n bool pivotEnabled\n ) internal view returns (uint256, bool) {\n (address mainOracle, bool mainOracleEnabled) = getOracle(asset, OracleRole.MAIN);\n if (mainOracleEnabled && mainOracle != address(0)) {\n try OracleInterface(mainOracle).getPrice(asset) returns (uint256 mainOraclePrice) {\n if (!pivotEnabled) {\n return (mainOraclePrice, true);\n }\n if (pivotPrice == INVALID_PRICE) {\n return (mainOraclePrice, false);\n }\n return (\n mainOraclePrice,\n boundValidator.validatePriceWithAnchorPrice(asset, mainOraclePrice, pivotPrice)\n );\n } catch {\n return (INVALID_PRICE, false);\n }\n }\n\n return (INVALID_PRICE, false);\n }\n\n /**\n * @dev This function won't revert when the price is 0 because getPrice checks if price is > 0\n * @param asset asset address\n * @return price USD price in 18 decimals\n * @return pivotValidated Boolean representing if the validation of fallback oracle price\n * and pivot oracle price were successfully\n * @custom:error Invalid price error is thrown if fallback oracle fails to fetch price of the asset\n * @custom:error Invalid price error is thrown if fallback oracle is not enabled or fallback oracle\n * address is null\n */\n function _getFallbackOraclePrice(address asset, uint256 pivotPrice) private view returns (uint256, bool) {\n (address fallbackOracle, bool fallbackEnabled) = getOracle(asset, OracleRole.FALLBACK);\n if (fallbackEnabled && fallbackOracle != address(0)) {\n try OracleInterface(fallbackOracle).getPrice(asset) returns (uint256 fallbackOraclePrice) {\n if (pivotPrice == INVALID_PRICE) {\n return (fallbackOraclePrice, false);\n }\n return (\n fallbackOraclePrice,\n boundValidator.validatePriceWithAnchorPrice(asset, fallbackOraclePrice, pivotPrice)\n );\n } catch {\n return (INVALID_PRICE, false);\n }\n }\n\n return (INVALID_PRICE, false);\n }\n\n /**\n * @dev This function returns the underlying asset of a vToken\n * @param vToken vToken address\n * @return asset underlying asset address\n */\n function _getUnderlyingAsset(address vToken) private view notNullAddress(vToken) returns (address asset) {\n if (vToken == nativeMarket) {\n asset = NATIVE_TOKEN_ADDR;\n } else if (vToken == vai) {\n asset = vai;\n } else {\n asset = VBep20Interface(vToken).underlying();\n }\n }\n\n /**\n * @dev This function checks if the asset price should be cached\n * @param asset asset address\n * @return bool true if caching is enabled, false otherwise\n */\n function _isCacheEnabled(address asset) private view returns (bool) {\n return tokenConfigs[asset].cachingEnabled;\n }\n}\n" + }, + "contracts/test/BEP20Harness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract BEP20Harness is ERC20 {\n uint8 public decimalsInternal = 18;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {\n decimalsInternal = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function decimals() public view virtual override returns (uint8) {\n return decimalsInternal;\n }\n}\n" + }, + "contracts/test/DeviationBoundedOracleCaller.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IDeviationBoundedOracle } from \"../interfaces/IDeviationBoundedOracle.sol\";\n\n/// @notice Test helper that batches DeviationBoundedOracle calls in a single transaction\n/// so transient storage (tstore/tload) cache can be tested.\ncontract DeviationBoundedOracleCaller {\n IDeviationBoundedOracle public immutable oracle;\n\n constructor(address _oracle) {\n oracle = IDeviationBoundedOracle(_oracle);\n }\n\n function updateAndGetCollateralPrice(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedCollateralPriceView(vToken);\n }\n\n function updateAndGetDebtPrice(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedDebtPriceView(vToken);\n }\n\n function updateAndGetBothPrices(address vToken) external returns (uint256 collateral, uint256 debt) {\n oracle.updateProtectionState(vToken);\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n function updateThenNonViewCollateral(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedCollateralPrice(vToken);\n }\n\n function twoConsecutiveNonViewCollateral(address vToken) external returns (uint256 first, uint256 second) {\n first = oracle.getBoundedCollateralPrice(vToken);\n second = oracle.getBoundedCollateralPrice(vToken);\n }\n\n /// @notice Non-view wrapper so smock records oracle calls made by the view functions.\n function getViewPricesWithoutUpdateNonView(address vToken) external returns (uint256 collateral, uint256 debt) {\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n /// @notice Calls updateProtectionState on vTokenA, then getBoundedCollateralPriceView on vTokenB.\n function updateAViewB(address vTokenA, address vTokenB) external returns (uint256) {\n oracle.updateProtectionState(vTokenA);\n return oracle.getBoundedCollateralPriceView(vTokenB);\n }\n\n function getViewPricesWithoutUpdate(address vToken) external view returns (uint256 collateral, uint256 debt) {\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n}\n" + }, + "contracts/test/MockAnkrBNB.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IAnkrBNB } from \"../interfaces/IAnkrBNB.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAnkrBNB is ERC20, Ownable, IAnkrBNB {\n uint8 private immutable _decimals;\n uint256 public exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setSharesToBonds(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function sharesToBonds(uint256 amount) external view override returns (uint256) {\n return (amount * exchangeRate) / (10 ** uint256(_decimals));\n }\n\n function decimals() public view virtual override(ERC20, IAnkrBNB) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockAsBNB.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IAsBNB } from \"../interfaces/IAsBNB.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAsBNB is ERC20, Ownable, IAsBNB {\n uint8 private immutable _decimals;\n address public minter;\n\n constructor(\n string memory name_,\n string memory symbol_,\n uint8 decimals_,\n address minter_\n ) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n minter = minter_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setMinter(address minter_) external onlyOwner {\n minter = minter_;\n }\n\n function decimals() public view virtual override(ERC20, IAsBNB) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockAsBNBMinter.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { IAsBNBMinter } from \"../interfaces/IAsBNBMinter.sol\";\n\ncontract MockAsBNBMinter is IAsBNBMinter {\n function convertToTokens(uint256 _amount) external pure override returns (uint256) {\n return _amount;\n }\n}\n" + }, + "contracts/test/MockCallPrice.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { OracleInterface, ResilientOracleInterface } from \"../interfaces/OracleInterface.sol\";\n\ninterface CorrelatedTokenOracleInterface {\n function updateSnapshot() external;\n function getPrice(address asset) external view returns (uint256);\n}\n\ncontract MockCallPrice {\n function getMultiPrice(CorrelatedTokenOracleInterface oracle, address asset) public returns (uint256, uint256) {\n oracle.updateSnapshot();\n return (oracle.getPrice(asset), oracle.getPrice(asset));\n }\n\n function getUnderlyingPriceResilientOracle(\n ResilientOracleInterface oracle,\n address vToken\n ) public returns (uint256, uint256) {\n oracle.updatePrice(vToken);\n oracle.updatePrice(vToken);\n return (oracle.getUnderlyingPrice(vToken), oracle.getUnderlyingPrice(vToken));\n }\n\n function getAssetPriceResilientOracle(\n ResilientOracleInterface oracle,\n address asset\n ) public returns (uint256, uint256) {\n oracle.updateAssetPrice(asset);\n oracle.updateAssetPrice(asset);\n return (oracle.getPrice(asset), oracle.getPrice(asset));\n }\n}\n" + }, + "contracts/test/MockEtherFiLiquidityPool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/IEtherFiLiquidityPool.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockEtherFiLiquidityPool is IEtherFiLiquidityPool, Ownable {\n /// @notice The amount of eETH per weETH scaled by 1e18\n uint256 public amountPerShare;\n\n constructor() Ownable() {}\n\n function setAmountPerShare(uint256 _amountPerShare) external onlyOwner {\n amountPerShare = _amountPerShare;\n }\n\n function amountForShare(uint256 _share) external view override returns (uint256) {\n return (_share * amountPerShare) / 1e18;\n }\n}\n" + }, + "contracts/test/MockSFrax.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { ISFrax } from \"../interfaces/ISFrax.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockSFrax is ERC20, Ownable, ISFrax {\n uint8 private immutable _decimals;\n uint256 public exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setRate(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function convertToAssets(uint256 shares) external view override returns (uint256) {\n return (shares * exchangeRate) / (10 ** uint256(_decimals));\n }\n\n function decimals() public view virtual override(ERC20, ISFrax) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockSimpleOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/OracleInterface.sol\";\n\ncontract MockSimpleOracle is OracleInterface {\n mapping(address => uint256) public prices;\n\n constructor() {\n //\n }\n\n function getUnderlyingPrice(address vToken) external view returns (uint256) {\n return prices[vToken];\n }\n\n function getPrice(address asset) external view returns (uint256) {\n return prices[asset];\n }\n\n function setPrice(address vToken, uint256 price) public {\n prices[vToken] = price;\n }\n}\n\ncontract MockBoundValidator is BoundValidatorInterface {\n mapping(address => bool) public validateResults;\n bool public twapUpdated;\n\n constructor() {\n //\n }\n\n function validatePriceWithAnchorPrice(\n address vToken,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool) {\n return validateResults[vToken];\n }\n\n function validateAssetPriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool) {\n return validateResults[asset];\n }\n\n function setValidateResult(address token, bool pass) public {\n validateResults[token] = pass;\n }\n}\n" + }, + "contracts/test/MockV3Aggregator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol\";\n\n/**\n * @title MockV3Aggregator\n * @notice Based on the FluxAggregator contract\n * @notice Use this contract when you need to test\n * other contract's ability to read data from an\n * aggregator contract, but how the aggregator got\n * its answer is unimportant\n */\ncontract MockV3Aggregator is AggregatorV2V3Interface {\n uint256 public constant version = 0;\n\n uint8 public decimals;\n int256 public latestAnswer;\n uint256 public latestTimestamp;\n uint256 public latestRound;\n\n mapping(uint256 => int256) public getAnswer;\n mapping(uint256 => uint256) public getTimestamp;\n mapping(uint256 => uint256) private getStartedAt;\n\n constructor(uint8 _decimals, int256 _initialAnswer) {\n decimals = _decimals;\n updateAnswer(_initialAnswer);\n }\n\n function getRoundData(\n uint80 _roundId\n )\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId);\n }\n\n function latestRoundData()\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n return (\n uint80(latestRound),\n getAnswer[latestRound],\n getStartedAt[latestRound],\n getTimestamp[latestRound],\n uint80(latestRound)\n );\n }\n\n function description() external pure returns (string memory) {\n return \"v0.6/tests/MockV3Aggregator.sol\";\n }\n\n function updateAnswer(int256 _answer) public {\n latestAnswer = _answer;\n latestTimestamp = block.timestamp;\n latestRound++;\n getAnswer[latestRound] = _answer;\n getTimestamp[latestRound] = block.timestamp;\n getStartedAt[latestRound] = block.timestamp;\n }\n\n function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public {\n latestRound = _roundId;\n latestAnswer = _answer;\n latestTimestamp = _timestamp;\n getAnswer[latestRound] = _answer;\n getTimestamp[latestRound] = _timestamp;\n getStartedAt[latestRound] = _startedAt;\n }\n}\n" + }, + "contracts/test/MockWBETH.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IWBETH } from \"../interfaces/IWBETH.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockWBETH is ERC20, Ownable, IWBETH {\n uint8 private immutable _decimals;\n uint256 public override exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setExchangeRate(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function decimals() public view virtual override(ERC20, IWBETH) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/oracles/MockCorrelatedTokenOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"../../oracles/common/CorrelatedTokenOracle.sol\";\n\ncontract MockCorrelatedTokenOracle is CorrelatedTokenOracle {\n uint256 public mockUnderlyingAmount;\n\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n snapshotGap\n )\n {}\n\n function setMockUnderlyingAmount(uint256 amount) external {\n mockUnderlyingAmount = amount;\n }\n\n function getUnderlyingAmount() public view override returns (uint256) {\n return mockUnderlyingAmount;\n }\n}\n" + }, + "contracts/test/oracles/MockERC20.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract MockERC20 is ERC20 {\n constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol) {\n _mint(msg.sender, 100000 * 10 ** uint256(decimals));\n }\n}\n" + }, + "contracts/test/oracles/MockResilientOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/OracleInterface.sol\";\n\ncontract MockOracle is OracleInterface {\n mapping(address => uint256) public prices;\n\n function getPrice(address asset) external view returns (uint256) {\n return prices[asset];\n }\n\n function setPrice(address vToken, uint256 price) public {\n prices[vToken] = price;\n }\n}\n" + }, + "contracts/test/PancakePairHarness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n// a library for performing various math operations\n\nlibrary Math {\n function min(uint256 x, uint256 y) internal pure returns (uint256 z) {\n z = x < y ? x : y;\n }\n\n // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)\n function sqrt(uint256 y) internal pure returns (uint256 z) {\n if (y > 3) {\n z = y;\n uint256 x = y / 2 + 1;\n while (x < z) {\n z = x;\n x = (y / x + x) / 2;\n }\n } else if (y != 0) {\n z = 1;\n }\n }\n}\n\n// range: [0, 2**112 - 1]\n// resolution: 1 / 2**112\n\nlibrary UQ112x112 {\n //solhint-disable-next-line state-visibility\n uint224 constant Q112 = 2 ** 112;\n\n // encode a uint112 as a UQ112x112\n function encode(uint112 y) internal pure returns (uint224 z) {\n z = uint224(y) * Q112; // never overflows\n }\n\n // divide a UQ112x112 by a uint112, returning a UQ112x112\n function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {\n z = x / uint224(y);\n }\n}\n\ncontract PancakePairHarness {\n using UQ112x112 for uint224;\n\n address public token0;\n address public token1;\n\n uint112 private reserve0; // uses single storage slot, accessible via getReserves\n uint112 private reserve1; // uses single storage slot, accessible via getReserves\n uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves\n\n uint256 public price0CumulativeLast;\n uint256 public price1CumulativeLast;\n uint256 public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event\n\n // called once by the factory at time of deployment\n function initialize(address _token0, address _token1) external {\n token0 = _token0;\n token1 = _token1;\n }\n\n // update reserves and, on the first call per block, price accumulators\n function update(uint256 balance0, uint256 balance1, uint112 _reserve0, uint112 _reserve1) external {\n require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, \"PancakeV2: OVERFLOW\");\n uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);\n unchecked {\n uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired\n if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {\n // * never overflows, and + overflow is desired\n price0CumulativeLast += uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;\n price1CumulativeLast += uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;\n }\n }\n reserve0 = uint112(balance0);\n reserve1 = uint112(balance1);\n blockTimestampLast = blockTimestamp;\n }\n\n function currentBlockTimestamp() external view returns (uint32) {\n return uint32(block.timestamp % 2 ** 32);\n }\n\n function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {\n _reserve0 = reserve0;\n _reserve1 = reserve1;\n _blockTimestampLast = blockTimestampLast;\n }\n}\n" + }, + "contracts/test/VBEP20Harness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"./BEP20Harness.sol\";\n\ncontract VBEP20Harness is BEP20Harness {\n /**\n * @notice Underlying asset for this VToken\n */\n address public underlying;\n\n constructor(\n string memory name_,\n string memory symbol_,\n uint8 decimals,\n address underlying_\n ) BEP20Harness(name_, symbol_, decimals) {\n underlying = underlying_;\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor (address initialOwner) {\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\n * proxy whose upgrades are fully controlled by the current implementation.\n */\ninterface IERC1822Proxiable {\n /**\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\n * address.\n *\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\n * function revert if invoked through a proxy.\n */\n function proxiableUUID() external view returns (bytes32);\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\n */\ninterface IBeacon {\n /**\n * @dev Must return an address that can be used as a delegate call target.\n *\n * {BeaconProxy} will check that this address is a contract.\n */\n function implementation() external view returns (address);\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../Proxy.sol\";\nimport \"./ERC1967Upgrade.sol\";\n\n/**\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\n * implementation address that can be changed. This address is stored in storage in the location specified by\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\n * implementation behind the proxy.\n */\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\n /**\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\n *\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\n */\n constructor(address _logic, bytes memory _data) payable {\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.implementation\")) - 1));\n _upgradeToAndCall(_logic, _data, false);\n }\n\n /**\n * @dev Returns the current implementation address.\n */\n function _implementation() internal view virtual override returns (address impl) {\n return ERC1967Upgrade._getImplementation();\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../beacon/IBeacon.sol\";\nimport \"../../interfaces/draft-IERC1822.sol\";\nimport \"../../utils/Address.sol\";\nimport \"../../utils/StorageSlot.sol\";\n\n/**\n * @dev This abstract contract provides getters and event emitting update functions for\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\n *\n * _Available since v4.1._\n *\n * @custom:oz-upgrades-unsafe-allow delegatecall\n */\nabstract contract ERC1967Upgrade {\n // This is the keccak-256 hash of \"eip1967.proxy.rollback\" subtracted by 1\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\n\n /**\n * @dev Storage slot with the address of the current implementation.\n * This is the keccak-256 hash of \"eip1967.proxy.implementation\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n\n /**\n * @dev Emitted when the implementation is upgraded.\n */\n event Upgraded(address indexed implementation);\n\n /**\n * @dev Returns the current implementation address.\n */\n function _getImplementation() internal view returns (address) {\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n }\n\n /**\n * @dev Stores a new address in the EIP1967 implementation slot.\n */\n function _setImplementation(address newImplementation) private {\n require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n }\n\n /**\n * @dev Perform implementation upgrade\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeTo(address newImplementation) internal {\n _setImplementation(newImplementation);\n emit Upgraded(newImplementation);\n }\n\n /**\n * @dev Perform implementation upgrade with additional setup call.\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeToAndCall(\n address newImplementation,\n bytes memory data,\n bool forceCall\n ) internal {\n _upgradeTo(newImplementation);\n if (data.length > 0 || forceCall) {\n Address.functionDelegateCall(newImplementation, data);\n }\n }\n\n /**\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeToAndCallUUPS(\n address newImplementation,\n bytes memory data,\n bool forceCall\n ) internal {\n // Upgrades from old implementations will perform a rollback test. This test requires the new\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\n // this special case will break upgrade paths from old UUPS implementation to new ones.\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\n _setImplementation(newImplementation);\n } else {\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\n require(slot == _IMPLEMENTATION_SLOT, \"ERC1967Upgrade: unsupported proxiableUUID\");\n } catch {\n revert(\"ERC1967Upgrade: new implementation is not UUPS\");\n }\n _upgradeToAndCall(newImplementation, data, forceCall);\n }\n }\n\n /**\n * @dev Storage slot with the admin of the contract.\n * This is the keccak-256 hash of \"eip1967.proxy.admin\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n /**\n * @dev Emitted when the admin account has changed.\n */\n event AdminChanged(address previousAdmin, address newAdmin);\n\n /**\n * @dev Returns the current admin.\n */\n function _getAdmin() internal view virtual returns (address) {\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\n }\n\n /**\n * @dev Stores a new address in the EIP1967 admin slot.\n */\n function _setAdmin(address newAdmin) private {\n require(newAdmin != address(0), \"ERC1967: new admin is the zero address\");\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\n }\n\n /**\n * @dev Changes the admin of the proxy.\n *\n * Emits an {AdminChanged} event.\n */\n function _changeAdmin(address newAdmin) internal {\n emit AdminChanged(_getAdmin(), newAdmin);\n _setAdmin(newAdmin);\n }\n\n /**\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\n */\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\n\n /**\n * @dev Emitted when the beacon is upgraded.\n */\n event BeaconUpgraded(address indexed beacon);\n\n /**\n * @dev Returns the current beacon.\n */\n function _getBeacon() internal view returns (address) {\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\n }\n\n /**\n * @dev Stores a new beacon in the EIP1967 beacon slot.\n */\n function _setBeacon(address newBeacon) private {\n require(Address.isContract(newBeacon), \"ERC1967: new beacon is not a contract\");\n require(Address.isContract(IBeacon(newBeacon).implementation()), \"ERC1967: beacon implementation is not a contract\");\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\n }\n\n /**\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\n *\n * Emits a {BeaconUpgraded} event.\n */\n function _upgradeBeaconToAndCall(\n address newBeacon,\n bytes memory data,\n bool forceCall\n ) internal {\n _setBeacon(newBeacon);\n emit BeaconUpgraded(newBeacon);\n if (data.length > 0 || forceCall) {\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/Proxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\n * be specified by overriding the virtual {_implementation} function.\n *\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\n * different contract through the {_delegate} function.\n *\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\n */\nabstract contract Proxy {\n /**\n * @dev Delegates the current call to `implementation`.\n *\n * This function does not return to its internal call site, it will return directly to the external caller.\n */\n function _delegate(address implementation) internal virtual {\n assembly {\n // Copy msg.data. We take full control of memory in this inline assembly\n // block because it will not return to Solidity code. We overwrite the\n // Solidity scratch pad at memory position 0.\n calldatacopy(0, 0, calldatasize())\n\n // Call the implementation.\n // out and outsize are 0 because we don't know the size yet.\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\n\n // Copy the returned data.\n returndatacopy(0, 0, returndatasize())\n\n switch result\n // delegatecall returns 0 on error.\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n\n /**\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\n * and {_fallback} should delegate.\n */\n function _implementation() internal view virtual returns (address);\n\n /**\n * @dev Delegates the current call to the address returned by `_implementation()`.\n *\n * This function does not return to its internall call site, it will return directly to the external caller.\n */\n function _fallback() internal virtual {\n _beforeFallback();\n _delegate(_implementation());\n }\n\n /**\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\n * function in the contract matches the call data.\n */\n fallback() external payable virtual {\n _fallback();\n }\n\n /**\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\n * is empty.\n */\n receive() external payable virtual {\n _fallback();\n }\n\n /**\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\n * call, or as part of the Solidity `fallback` or `receive` functions.\n *\n * If overriden should call `super._beforeFallback()`.\n */\n function _beforeFallback() internal virtual {}\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./TransparentUpgradeableProxy.sol\";\nimport \"../../access/Ownable.sol\";\n\n/**\n * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an\n * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.\n */\ncontract ProxyAdmin is Ownable {\n\n constructor (address initialOwner) Ownable(initialOwner) {}\n\n /**\n * @dev Returns the current implementation of `proxy`.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {\n // We need to manually run the static call since the getter cannot be flagged as view\n // bytes4(keccak256(\"implementation()\")) == 0x5c60da1b\n (bool success, bytes memory returndata) = address(proxy).staticcall(hex\"5c60da1b\");\n require(success);\n return abi.decode(returndata, (address));\n }\n\n /**\n * @dev Returns the current admin of `proxy`.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {\n // We need to manually run the static call since the getter cannot be flagged as view\n // bytes4(keccak256(\"admin()\")) == 0xf851a440\n (bool success, bytes memory returndata) = address(proxy).staticcall(hex\"f851a440\");\n require(success);\n return abi.decode(returndata, (address));\n }\n\n /**\n * @dev Changes the admin of `proxy` to `newAdmin`.\n *\n * Requirements:\n *\n * - This contract must be the current admin of `proxy`.\n */\n function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {\n proxy.changeAdmin(newAdmin);\n }\n\n /**\n * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {\n proxy.upgradeTo(implementation);\n }\n\n /**\n * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See\n * {TransparentUpgradeableProxy-upgradeToAndCall}.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function upgradeAndCall(\n TransparentUpgradeableProxy proxy,\n address implementation,\n bytes memory data\n ) public payable virtual onlyOwner {\n proxy.upgradeToAndCall{value: msg.value}(implementation, data);\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../ERC1967/ERC1967Proxy.sol\";\n\n/**\n * @dev This contract implements a proxy that is upgradeable by an admin.\n *\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\n * clashing], which can potentially be used in an attack, this contract uses the\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\n * things that go hand in hand:\n *\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\n * that call matches one of the admin functions exposed by the proxy itself.\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\n * \"admin cannot fallback to proxy target\".\n *\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\n * to sudden errors when trying to call a function from the proxy implementation.\n *\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\n */\ncontract TransparentUpgradeableProxy is ERC1967Proxy {\n /**\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\n */\n constructor(\n address _logic,\n address admin_,\n bytes memory _data\n ) payable ERC1967Proxy(_logic, _data) {\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n _changeAdmin(admin_);\n }\n\n /**\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\n */\n modifier ifAdmin() {\n if (msg.sender == _getAdmin()) {\n _;\n } else {\n _fallback();\n }\n }\n\n /**\n * @dev Returns the current admin.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\n */\n function admin() external ifAdmin returns (address admin_) {\n admin_ = _getAdmin();\n }\n\n /**\n * @dev Returns the current implementation.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\n */\n function implementation() external ifAdmin returns (address implementation_) {\n implementation_ = _implementation();\n }\n\n /**\n * @dev Changes the admin of the proxy.\n *\n * Emits an {AdminChanged} event.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.\n */\n function changeAdmin(address newAdmin) external virtual ifAdmin {\n _changeAdmin(newAdmin);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\n */\n function upgradeTo(address newImplementation) external ifAdmin {\n _upgradeToAndCall(newImplementation, bytes(\"\"), false);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\n * proxied contract.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\n _upgradeToAndCall(newImplementation, data, true);\n }\n\n /**\n * @dev Returns the current admin.\n */\n function _admin() internal view virtual returns (address) {\n return _getAdmin();\n }\n\n /**\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\n */\n function _beforeFallback() internal virtual override {\n require(msg.sender != _getAdmin(), \"TransparentUpgradeableProxy: admin cannot fallback to proxy target\");\n super._beforeFallback();\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/StorageSlot.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC1967 implementation slot:\n * ```\n * contract ERC1967 {\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\";\n\n/**\n * @dev This contract implements a proxy that is upgradeable by an admin.\n *\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\n * clashing], which can potentially be used in an attack, this contract uses the\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\n * things that go hand in hand:\n *\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\n * that call matches one of the admin functions exposed by the proxy itself.\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\n * \"admin cannot fallback to proxy target\".\n *\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\n * to sudden errors when trying to call a function from the proxy implementation.\n *\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\n */\ncontract OptimizedTransparentUpgradeableProxy is ERC1967Proxy {\n address internal immutable _ADMIN;\n\n /**\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\n */\n constructor(\n address _logic,\n address admin_,\n bytes memory _data\n ) payable ERC1967Proxy(_logic, _data) {\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n _ADMIN = admin_;\n\n // still store it to work with EIP-1967\n bytes32 slot = _ADMIN_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(slot, admin_)\n }\n emit AdminChanged(address(0), admin_);\n }\n\n /**\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\n */\n modifier ifAdmin() {\n if (msg.sender == _getAdmin()) {\n _;\n } else {\n _fallback();\n }\n }\n\n /**\n * @dev Returns the current admin.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\n */\n function admin() external ifAdmin returns (address admin_) {\n admin_ = _getAdmin();\n }\n\n /**\n * @dev Returns the current implementation.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\n */\n function implementation() external ifAdmin returns (address implementation_) {\n implementation_ = _implementation();\n }\n\n /**\n * @dev Upgrade the implementation of the proxy.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\n */\n function upgradeTo(address newImplementation) external ifAdmin {\n _upgradeToAndCall(newImplementation, bytes(\"\"), false);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\n * proxied contract.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\n _upgradeToAndCall(newImplementation, data, true);\n }\n\n /**\n * @dev Returns the current admin.\n */\n function _admin() internal view virtual returns (address) {\n return _getAdmin();\n }\n\n /**\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\n */\n function _beforeFallback() internal virtual override {\n require(msg.sender != _getAdmin(), \"TransparentUpgradeableProxy: admin cannot fallback to proxy target\");\n super._beforeFallback();\n }\n\n function _getAdmin() internal view virtual override returns (address) {\n return _ADMIN;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200, + "details": { + "yul": true + } + }, + "evmVersion": "cancun", + "outputSelection": { + "*": { + "*": [ + "storageLayout", + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "evm.gasEstimates" + ], + "": ["ast"] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} diff --git a/deployments/bscmainnet_addresses.json b/deployments/bscmainnet_addresses.json index 5c46c523..516cb249 100644 --- a/deployments/bscmainnet_addresses.json +++ b/deployments/bscmainnet_addresses.json @@ -16,6 +16,9 @@ "ChainlinkOracle_Proxy": "0x1B2103441A0A108daD8848D8F5d790e4D402921F", "DefaultProxyAdmin": "0x1BB765b741A5f3C2A338369DAb539385534E3343", "DevProxyAdmin": "0x10BA1AA0F56790835a3462371cCbeAa7A4BA1e46", + "DeviationBoundedOracle": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", + "DeviationBoundedOracle_Implementation": "0x16691f500541ca35bd63DD878B6D78728C9518AE", + "DeviationBoundedOracle_Proxy": "0xc79Cb7efEBd121DC4B39eA141C214606595D665A", "PendleOracle-PT-SolvBTC.BBN-27MAR2025": "0xE11965a3513F537d91D73d9976FBe8c0969Bb252", "PendleOracle-PT-SolvBTC.BBN-27MAR2025_Implementation": "0xD2721FB0d9F071d84B3EbFd27Ab35B568b350079", "PendleOracle-PT-SolvBTC.BBN-27MAR2025_Proxy": "0xE11965a3513F537d91D73d9976FBe8c0969Bb252", diff --git a/deployments/bsctestnet.json b/deployments/bsctestnet.json index 18c1e5bb..e519494e 100644 --- a/deployments/bsctestnet.json +++ b/deployments/bsctestnet.json @@ -4488,6 +4488,2893 @@ } ] }, + "DeviationBoundedOracle": { + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint64[]", + "name": "cooldownPeriods", + "type": "uint64[]" + }, + { + "internalType": "uint256[]", + "name": "triggerThresholds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "resetThresholds", + "type": "uint256[]" + }, + { + "internalType": "bool[]", + "name": "enableBoundedPricings", + "type": "bool[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } + ] + }, + "DeviationBoundedOracle_Implementation": { + "address": "0x90c9756446ebA9E1762811c239Fe10029019e35e", + "abi": [ + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "_resilientOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "nativeMarketAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaiAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidKeeperAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newEnabled", + "type": "bool" + } + ], + "name": "CachingEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setCachingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput", + "name": "tokenConfig_", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum IDeviationBoundedOracle.KeeperAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IDeviationBoundedOracle.KeeperActionItem[]", + "name": "actions", + "type": "tuple[]" + } + ], + "name": "syncPriceBoundsAndProtections", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ] + }, + "DeviationBoundedOracle_Proxy": { + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] + }, "MockAnkrBNB": { "address": "0x5269b7558D3d5E113010Ef1cFF0901c367849CC9", "abi": [ diff --git a/deployments/bsctestnet/DeviationBoundedOracle.json b/deployments/bsctestnet/DeviationBoundedOracle.json new file mode 100644 index 00000000..f079fd70 --- /dev/null +++ b/deployments/bsctestnet/DeviationBoundedOracle.json @@ -0,0 +1,1505 @@ +{ + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint64[]", + "name": "cooldownPeriods", + "type": "uint64[]" + }, + { + "internalType": "uint256[]", + "name": "triggerThresholds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "resetThresholds", + "type": "uint256[]" + }, + { + "internalType": "bool[]", + "name": "enableBoundedPricings", + "type": "bool[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } + ], + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "receipt": { + "to": null, + "from": "0x4cD6300F5cb8D6BbA5E646131c3522664C10dF11", + "contractAddress": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "transactionIndex": 0, + "gasUsed": "589317", + "logsBloom": "0x00000000000000000000000000000000400000000000000000800000000000000000000000000000000000000020000000000000000000000000000000008000000000000000000000000000000002000001000000000000000000000000000000000000020000000000000000000800000000800000000000000000000000400000000000000000000000000002000000100000000080000000000000800000000000000000000000000000080440000000000000800000000000000000000000000020008000000000000000040000000000000400000000000000200020001000000000000000000000000000000000000880000000000000000000000000", + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32", + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "logs": [ + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x00000000000000000000000028f9576ec8d73331cda7f0a6fac88b0ca4d41e3f" + ], + "data": "0x", + "logIndex": 0, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + }, + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000004cd6300f5cb8d6bba5e646131c3522664c10df11" + ], + "data": "0x", + "logIndex": 1, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + }, + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": ["0x66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa0"], + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045f8a08f534f34a97187626e05d4b6648eeaa9aa", + "logIndex": 2, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + }, + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 3, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + }, + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ef480a5654b231ff7d80a0681f938f3db71a6ca6", + "logIndex": 4, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + } + ], + "blockNumber": 102770929, + "cumulativeGasUsed": "589317", + "status": 1, + "byzantium": true + }, + "args": [ + "0x28f9576ec8D73331CDa7F0A6fAc88b0cA4D41e3f", + "0xef480a5654b231ff7d80A0681F938f3Db71a6Ca6", + "0xc4d66de800000000000000000000000045f8a08f534f34a97187626e05d4b6648eeaa9aa" + ], + "numDeployments": 1, + "solcInputHash": "fad126030085a626952462c92d5fc265", + "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"implementation_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"details\":\"This contract implements a proxy that is upgradeable by an admin. 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.\",\"events\":{\"AdminChanged(address,address)\":{\"details\":\"Emitted when the admin account has changed.\"},\"BeaconUpgraded(address)\":{\"details\":\"Emitted when the beacon is upgraded.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"admin()\":{\"details\":\"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`\"},\"constructor\":{\"details\":\"Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\"},\"implementation()\":{\"details\":\"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`\"},\"upgradeTo(address)\":{\"details\":\"Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\"},\"upgradeToAndCall(address,bytes)\":{\"details\":\"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}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol\":\"OptimizedTransparentUpgradeableProxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"hardhat-deploy/solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\\n * proxy whose upgrades are fully controlled by the current implementation.\\n */\\ninterface IERC1822Proxiable {\\n /**\\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\\n * address.\\n *\\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\\n * function revert if invoked through a proxy.\\n */\\n function proxiableUUID() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x93b4e21c931252739a1ec13ea31d3d35a5c068be3163ccab83e4d70c40355f03\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Proxy.sol\\\";\\nimport \\\"./ERC1967Upgrade.sol\\\";\\n\\n/**\\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\\n * implementation address that can be changed. This address is stored in storage in the location specified by\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\\n * implementation behind the proxy.\\n */\\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\\n /**\\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\\n *\\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\\n */\\n constructor(address _logic, bytes memory _data) payable {\\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.implementation\\\")) - 1));\\n _upgradeToAndCall(_logic, _data, false);\\n }\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _implementation() internal view virtual override returns (address impl) {\\n return ERC1967Upgrade._getImplementation();\\n }\\n}\\n\",\"keccak256\":\"0x6309f9f39dc6f4f45a24f296543867aa358e32946cd6b2874627a996d606b3a0\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../beacon/IBeacon.sol\\\";\\nimport \\\"../../interfaces/draft-IERC1822.sol\\\";\\nimport \\\"../../utils/Address.sol\\\";\\nimport \\\"../../utils/StorageSlot.sol\\\";\\n\\n/**\\n * @dev This abstract contract provides getters and event emitting update functions for\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\\n *\\n * _Available since v4.1._\\n *\\n * @custom:oz-upgrades-unsafe-allow delegatecall\\n */\\nabstract contract ERC1967Upgrade {\\n // This is the keccak-256 hash of \\\"eip1967.proxy.rollback\\\" subtracted by 1\\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\\n\\n /**\\n * @dev Storage slot with the address of the current implementation.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.implementation\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n\\n /**\\n * @dev Emitted when the implementation is upgraded.\\n */\\n event Upgraded(address indexed implementation);\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _getImplementation() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 implementation slot.\\n */\\n function _setImplementation(address newImplementation) private {\\n require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n }\\n\\n /**\\n * @dev Perform implementation upgrade\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeTo(address newImplementation) internal {\\n _setImplementation(newImplementation);\\n emit Upgraded(newImplementation);\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCall(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _upgradeTo(newImplementation);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(newImplementation, data);\\n }\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCallUUPS(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n // Upgrades from old implementations will perform a rollback test. This test requires the new\\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\\n // this special case will break upgrade paths from old UUPS implementation to new ones.\\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\\n _setImplementation(newImplementation);\\n } else {\\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\\n require(slot == _IMPLEMENTATION_SLOT, \\\"ERC1967Upgrade: unsupported proxiableUUID\\\");\\n } catch {\\n revert(\\\"ERC1967Upgrade: new implementation is not UUPS\\\");\\n }\\n _upgradeToAndCall(newImplementation, data, forceCall);\\n }\\n }\\n\\n /**\\n * @dev Storage slot with the admin of the contract.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.admin\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\\n\\n /**\\n * @dev Emitted when the admin account has changed.\\n */\\n event AdminChanged(address previousAdmin, address newAdmin);\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _getAdmin() internal view virtual returns (address) {\\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 admin slot.\\n */\\n function _setAdmin(address newAdmin) private {\\n require(newAdmin != address(0), \\\"ERC1967: new admin is the zero address\\\");\\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\\n }\\n\\n /**\\n * @dev Changes the admin of the proxy.\\n *\\n * Emits an {AdminChanged} event.\\n */\\n function _changeAdmin(address newAdmin) internal {\\n emit AdminChanged(_getAdmin(), newAdmin);\\n _setAdmin(newAdmin);\\n }\\n\\n /**\\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\\n */\\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\\n\\n /**\\n * @dev Emitted when the beacon is upgraded.\\n */\\n event BeaconUpgraded(address indexed beacon);\\n\\n /**\\n * @dev Returns the current beacon.\\n */\\n function _getBeacon() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new beacon in the EIP1967 beacon slot.\\n */\\n function _setBeacon(address newBeacon) private {\\n require(Address.isContract(newBeacon), \\\"ERC1967: new beacon is not a contract\\\");\\n require(Address.isContract(IBeacon(newBeacon).implementation()), \\\"ERC1967: beacon implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\\n }\\n\\n /**\\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\\n *\\n * Emits a {BeaconUpgraded} event.\\n */\\n function _upgradeBeaconToAndCall(\\n address newBeacon,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _setBeacon(newBeacon);\\n emit BeaconUpgraded(newBeacon);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x17668652127feebed0ce8d9431ef95ccc8c4292f03e3b8cf06c6ca16af396633\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\\n * be specified by overriding the virtual {_implementation} function.\\n *\\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\\n * different contract through the {_delegate} function.\\n *\\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\\n */\\nabstract contract Proxy {\\n /**\\n * @dev Delegates the current call to `implementation`.\\n *\\n * This function does not return to its internal call site, it will return directly to the external caller.\\n */\\n function _delegate(address implementation) internal virtual {\\n assembly {\\n // Copy msg.data. We take full control of memory in this inline assembly\\n // block because it will not return to Solidity code. We overwrite the\\n // Solidity scratch pad at memory position 0.\\n calldatacopy(0, 0, calldatasize())\\n\\n // Call the implementation.\\n // out and outsize are 0 because we don't know the size yet.\\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\\n\\n // Copy the returned data.\\n returndatacopy(0, 0, returndatasize())\\n\\n switch result\\n // delegatecall returns 0 on error.\\n case 0 {\\n revert(0, returndatasize())\\n }\\n default {\\n return(0, returndatasize())\\n }\\n }\\n }\\n\\n /**\\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\\n * and {_fallback} should delegate.\\n */\\n function _implementation() internal view virtual returns (address);\\n\\n /**\\n * @dev Delegates the current call to the address returned by `_implementation()`.\\n *\\n * This function does not return to its internall call site, it will return directly to the external caller.\\n */\\n function _fallback() internal virtual {\\n _beforeFallback();\\n _delegate(_implementation());\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\\n * function in the contract matches the call data.\\n */\\n fallback() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\\n * is empty.\\n */\\n receive() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\\n * call, or as part of the Solidity `fallback` or `receive` functions.\\n *\\n * If overriden should call `super._beforeFallback()`.\\n */\\n function _beforeFallback() internal virtual {}\\n}\\n\",\"keccak256\":\"0xd5d1fd16e9faff7fcb3a52e02a8d49156f42a38a03f07b5f1810c21c2149a8ab\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\\n */\\ninterface IBeacon {\\n /**\\n * @dev Must return an address that can be used as a delegate call target.\\n *\\n * {BeaconProxy} will check that this address is a contract.\\n */\\n function implementation() external view returns (address);\\n}\\n\",\"keccak256\":\"0xd50a3421ac379ccb1be435fa646d66a65c986b4924f0849839f08692f39dde61\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3777e696b62134e6177440dbe6e6601c0c156a443f57167194b67e75527439de\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/utils/StorageSlot.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for reading and writing primitive types to specific storage slots.\\n *\\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\\n * This library helps with reading and writing to such slots without the need for inline assembly.\\n *\\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\\n *\\n * Example usage to set ERC1967 implementation slot:\\n * ```\\n * contract ERC1967 {\\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n *\\n * function _getImplementation() internal view returns (address) {\\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n * }\\n *\\n * function _setImplementation(address newImplementation) internal {\\n * require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n * }\\n * }\\n * ```\\n *\\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\\n */\\nlibrary StorageSlot {\\n struct AddressSlot {\\n address value;\\n }\\n\\n struct BooleanSlot {\\n bool value;\\n }\\n\\n struct Bytes32Slot {\\n bytes32 value;\\n }\\n\\n struct Uint256Slot {\\n uint256 value;\\n }\\n\\n /**\\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\\n */\\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\\n */\\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\\n */\\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\\n */\\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfe1b7a9aa2a530a9e705b220e26cd584e2fbdc9602a3a1066032b12816b46aca\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\\\";\\n\\n/**\\n * @dev This contract implements a proxy that is upgradeable by an admin.\\n *\\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\\n * clashing], which can potentially be used in an attack, this contract uses the\\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\\n * things that go hand in hand:\\n *\\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\\n * that call matches one of the admin functions exposed by the proxy itself.\\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\\n * \\\"admin cannot fallback to proxy target\\\".\\n *\\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\\n * to sudden errors when trying to call a function from the proxy implementation.\\n *\\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\\n */\\ncontract OptimizedTransparentUpgradeableProxy is ERC1967Proxy {\\n address internal immutable _ADMIN;\\n\\n /**\\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\\n */\\n constructor(\\n address _logic,\\n address admin_,\\n bytes memory _data\\n ) payable ERC1967Proxy(_logic, _data) {\\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.admin\\\")) - 1));\\n _ADMIN = admin_;\\n\\n // still store it to work with EIP-1967\\n bytes32 slot = _ADMIN_SLOT;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(slot, admin_)\\n }\\n emit AdminChanged(address(0), admin_);\\n }\\n\\n /**\\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\\n */\\n modifier ifAdmin() {\\n if (msg.sender == _getAdmin()) {\\n _;\\n } else {\\n _fallback();\\n }\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\\n */\\n function admin() external ifAdmin returns (address admin_) {\\n admin_ = _getAdmin();\\n }\\n\\n /**\\n * @dev Returns the current implementation.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\\n */\\n function implementation() external ifAdmin returns (address implementation_) {\\n implementation_ = _implementation();\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\\n */\\n function upgradeTo(address newImplementation) external ifAdmin {\\n _upgradeToAndCall(newImplementation, bytes(\\\"\\\"), false);\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\\n * proxied contract.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\\n */\\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\\n _upgradeToAndCall(newImplementation, data, true);\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _admin() internal view virtual returns (address) {\\n return _getAdmin();\\n }\\n\\n /**\\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\\n */\\n function _beforeFallback() internal virtual override {\\n require(msg.sender != _getAdmin(), \\\"TransparentUpgradeableProxy: admin cannot fallback to proxy target\\\");\\n super._beforeFallback();\\n }\\n\\n function _getAdmin() internal view virtual override returns (address) {\\n return _ADMIN;\\n }\\n}\\n\",\"keccak256\":\"0xa30117644e27fa5b49e162aae2f62b36c1aca02f801b8c594d46e2024963a534\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60a0604052604051610c8f380380610c8f8339810160408190526100229161039f565b828161004f60017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd61046a565b5f80516020610c488339815191521461006a5761006a610489565b61007582825f610123565b506100a3905060017fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610461046a565b5f80516020610c28833981519152146100be576100be610489565b6001600160a01b03821660808190525f80516020610c28833981519152838155604080515f8152602081019390935290917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050506104e8565b61012c8361014e565b5f825111806101385750805b1561014957610147838361018d565b505b505050565b610157816101bb565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606101b28383604051806060016040528060278152602001610c686027913961025b565b90505b92915050565b6001600160a01b0381163b61022d5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b5f80516020610c4883398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6102c35760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610224565b5f80856001600160a01b0316856040516102dd919061049d565b5f60405180830381855af49150503d805f8114610315576040519150601f19603f3d011682016040523d82523d5f602084013e61031a565b606091505b50909250905061032b828286610337565b925050505b9392505050565b60608315610346575081610330565b8251156103565782518084602001fd5b8160405162461bcd60e51b815260040161022491906104b3565b80516001600160a01b0381168114610386575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f805f606084860312156103b1575f80fd5b6103ba84610370565b92506103c860208501610370565b60408501519092506001600160401b03808211156103e4575f80fd5b818601915086601f8301126103f7575f80fd5b8151818111156104095761040961038b565b604051601f8201601f19908116603f011681019083821181831017156104315761043161038b565b81604052828152896020848701011115610449575f80fd5b8260208601602083015e5f6020848301015280955050505050509250925092565b818103818111156101b557634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52600160045260245ffd5b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b6080516107066105225f395f818160eb0152818161013f015281816101bf0152818161020801528181610239015261025d01526107065ff3fe608060405260043610610042575f3560e01c80633659cfe6146100595780634f1ef286146100785780635c60da1b1461008b578063f851a440146100bb57610051565b366100515761004f6100cf565b005b61004f6100cf565b348015610064575f80fd5b5061004f6100733660046105c9565b6100e9565b61004f6100863660046105e2565b61013d565b348015610096575f80fd5b5061009f6101bc565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c6575f80fd5b5061009f610205565b6100d761025b565b6100e76100e2610309565b61033b565b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610135576101328160405180602001604052805f8152505f610359565b50565b6101326100cf565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101b4576101af8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610359915050565b505050565b6101af6100cf565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa576101f5610309565b905090565b6102026100cf565b90565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa57507f000000000000000000000000000000000000000000000000000000000000000090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036100e75760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101f57f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b365f80375f80365f845af43d5f803e808015610355573d5ff35b3d5ffd5b61036283610383565b5f8251118061036e5750805b156101af5761037d83836103c2565b50505050565b61038c816103ee565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103e783836040518060600160405280602781526020016106aa6027913961049c565b9392505050565b6001600160a01b0381163b61045b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610300565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6105045760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610300565b5f80856001600160a01b03168560405161051e919061065e565b5f60405180830381855af49150503d805f8114610556576040519150601f19603f3d011682016040523d82523d5f602084013e61055b565b606091505b509150915061056b828286610575565b9695505050505050565b606083156105845750816103e7565b8251156105945782518084602001fd5b8160405162461bcd60e51b81526004016103009190610674565b80356001600160a01b03811681146105c4575f80fd5b919050565b5f602082840312156105d9575f80fd5b6103e7826105ae565b5f805f604084860312156105f4575f80fd5b6105fd846105ae565b9250602084013567ffffffffffffffff80821115610619575f80fd5b818601915086601f83011261062c575f80fd5b81358181111561063a575f80fd5b87602082850101111561064b575f80fd5b6020830194508093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d447ec2a4b47962d9e959ca0ec6571d77747999259501e773b66fdcaef0e50bf64736f6c63430008190033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", + "deployedBytecode": "0x608060405260043610610042575f3560e01c80633659cfe6146100595780634f1ef286146100785780635c60da1b1461008b578063f851a440146100bb57610051565b366100515761004f6100cf565b005b61004f6100cf565b348015610064575f80fd5b5061004f6100733660046105c9565b6100e9565b61004f6100863660046105e2565b61013d565b348015610096575f80fd5b5061009f6101bc565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c6575f80fd5b5061009f610205565b6100d761025b565b6100e76100e2610309565b61033b565b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610135576101328160405180602001604052805f8152505f610359565b50565b6101326100cf565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101b4576101af8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610359915050565b505050565b6101af6100cf565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa576101f5610309565b905090565b6102026100cf565b90565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa57507f000000000000000000000000000000000000000000000000000000000000000090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036100e75760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101f57f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b365f80375f80365f845af43d5f803e808015610355573d5ff35b3d5ffd5b61036283610383565b5f8251118061036e5750805b156101af5761037d83836103c2565b50505050565b61038c816103ee565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103e783836040518060600160405280602781526020016106aa6027913961049c565b9392505050565b6001600160a01b0381163b61045b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610300565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6105045760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610300565b5f80856001600160a01b03168560405161051e919061065e565b5f60405180830381855af49150503d805f8114610556576040519150601f19603f3d011682016040523d82523d5f602084013e61055b565b606091505b509150915061056b828286610575565b9695505050505050565b606083156105845750816103e7565b8251156105945782518084602001fd5b8160405162461bcd60e51b81526004016103009190610674565b80356001600160a01b03811681146105c4575f80fd5b919050565b5f602082840312156105d9575f80fd5b6103e7826105ae565b5f805f604084860312156105f4575f80fd5b6105fd846105ae565b9250602084013567ffffffffffffffff80821115610619575f80fd5b818601915086601f83011261062c575f80fd5b81358181111561063a575f80fd5b87602082850101111561064b575f80fd5b6020830194508093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d447ec2a4b47962d9e959ca0ec6571d77747999259501e773b66fdcaef0e50bf64736f6c63430008190033", + "execute": { + "methodName": "initialize", + "args": ["0x45f8a08F534f34A97187626E05d4b6648Eeaa9AA"] + }, + "implementation": "0x28f9576ec8D73331CDa7F0A6fAc88b0cA4D41e3f", + "devdoc": { + "details": "This contract implements a proxy that is upgradeable by an admin. 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.", + "events": { + "AdminChanged(address,address)": { + "details": "Emitted when the admin account has changed." + }, + "BeaconUpgraded(address)": { + "details": "Emitted when the beacon is upgraded." + }, + "Upgraded(address)": { + "details": "Emitted when the implementation is upgraded." + } + }, + "kind": "dev", + "methods": { + "admin()": { + "details": "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`" + }, + "constructor": { + "details": "Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}." + }, + "implementation()": { + "details": "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`" + }, + "upgradeTo(address)": { + "details": "Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}." + }, + "upgradeToAndCall(address,bytes)": { + "details": "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}." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} diff --git a/deployments/bsctestnet/DeviationBoundedOracle_Implementation.json b/deployments/bsctestnet/DeviationBoundedOracle_Implementation.json new file mode 100644 index 00000000..0c27e876 --- /dev/null +++ b/deployments/bsctestnet/DeviationBoundedOracle_Implementation.json @@ -0,0 +1,2191 @@ +{ + "address": "0x90c9756446ebA9E1762811c239Fe10029019e35e", + "abi": [ + { + "inputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "_resilientOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "nativeMarketAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "vaiAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + } + ], + "name": "CooldownNotElapsed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "action", + "type": "uint8" + } + ], + "name": "InvalidKeeperAction", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMaxPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "currentSpot", + "type": "uint256" + } + ], + "name": "InvalidMinPrice", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "InvalidResetThreshold", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "MarketNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "PriceExceedsUint128", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentRangeRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + } + ], + "name": "PriceRangeNotConverged", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceActive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectedPriceInactive", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maximum", + "type": "uint256" + } + ], + "name": "ThresholdAboveMaximum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "threshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minimum", + "type": "uint256" + } + ], + "name": "ThresholdBelowMinimum", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "calledContract", + "type": "address" + }, + { + "internalType": "string", + "name": "methodSignature", + "type": "string" + } + ], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "VAINotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPriceNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroValueNotAllowed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "BoundedPricingWhitelistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldEnabled", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newEnabled", + "type": "bool" + } + ], + "name": "CachingEnabledUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "oldCooldown", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "CooldownPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMax", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "MaxPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "oldMin", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "MinPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldAccessControlManager", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAccessControlManager", + "type": "address" + } + ], + "name": "NewAccessControlManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + } + ], + "name": "ProtectionInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "ProtectionModeExited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "spotPrice", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + } + ], + "name": "ProtectionTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldExitThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newExitThreshold", + "type": "uint256" + } + ], + "name": "ResetThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldThreshold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "TriggerThresholdSet", + "type": "event" + }, + { + "inputs": [], + "name": "COLLATERAL_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEBT_PRICE_CACHE_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KEEPER_DEADBAND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NATIVE_TOKEN_ADDR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESILIENT_ORACLE", + "outputs": [ + { + "internalType": "contract ResilientOracleInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessControlManager", + "outputs": [ + { + "internalType": "contract IAccessControlManagerV8", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allAssets", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetProtectionConfig", + "outputs": [ + { + "internalType": "uint128", + "name": "minPrice", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "maxPrice", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "currentlyUsingProtectedPrice", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isBoundedPricingEnabled", + "type": "bool" + }, + { + "internalType": "uint64", + "name": "lastProtectionTriggeredAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "triggerThreshold", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "resetThreshold", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "cachingEnabled", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "canExitProtection", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMins", + "type": "uint128[]" + }, + { + "internalType": "uint128[]", + "name": "proposedMaxs", + "type": "uint128[]" + } + ], + "name": "checkAndGetWindowDrift", + "outputs": [ + { + "internalType": "bool[]", + "name": "needsMinUpdate", + "type": "bool[]" + }, + { + "internalType": "bool[]", + "name": "needsMaxUpdate", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "currentlyUsingProtectedPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "exitProtectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllBoundedPricingEnabledAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedCollateralPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedDebtPriceView", + "outputs": [ + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPrices", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "getBoundedPricesView", + "outputs": [ + { + "internalType": "uint256", + "name": "collateralPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtPrice", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInitializedAssets", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "name": "isBoundedPricingEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeMarket", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "accessControlManager_", + "type": "address" + } + ], + "name": "setAccessControlManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setAssetBoundedPricingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setCachingEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "newCooldown", + "type": "uint64" + } + ], + "name": "setCooldownPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newTriggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newResetThreshold", + "type": "uint256" + } + ], + "name": "setThresholds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput", + "name": "tokenConfig_", + "type": "tuple" + } + ], + "name": "setTokenConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "cooldownPeriod", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "triggerThreshold", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resetThreshold", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "enableBoundedPricing", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableCaching", + "type": "bool" + } + ], + "internalType": "struct IDeviationBoundedOracle.TokenConfigInput[]", + "name": "tokenConfigs_", + "type": "tuple[]" + } + ], + "name": "setTokenConfigs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "enum IDeviationBoundedOracle.KeeperAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct IDeviationBoundedOracle.KeeperActionItem[]", + "name": "actions", + "type": "tuple[]" + } + ], + "name": "syncPriceBoundsAndProtections", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMax", + "type": "uint128" + } + ], + "name": "updateMaxPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint128", + "name": "newMin", + "type": "uint128" + } + ], + "name": "updateMinPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vToken", + "type": "address" + } + ], + "name": "updateProtectionState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vai", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0xb6c6a5a64618e32b7b9f1301c4fb1dcdcbbaa9663a1a654fe445bb34708b9450", + "receipt": { + "to": null, + "from": "0xe2a089cA69a90f1E27E723EFD339Cff4c4701AcC", + "contractAddress": "0x90c9756446ebA9E1762811c239Fe10029019e35e", + "transactionIndex": 0, + "gasUsed": "2869841", + "logsBloom": "0x00000000000000000000000000000000000080004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x19e89674081d676bfe8e6d4f611299347cfdb923db79beff1749838ea5053c37", + "transactionHash": "0xb6c6a5a64618e32b7b9f1301c4fb1dcdcbbaa9663a1a654fe445bb34708b9450", + "logs": [ + { + "transactionIndex": 0, + "blockNumber": 104860903, + "transactionHash": "0xb6c6a5a64618e32b7b9f1301c4fb1dcdcbbaa9663a1a654fe445bb34708b9450", + "address": "0x90c9756446ebA9E1762811c239Fe10029019e35e", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x00000000000000000000000000000000000000000000000000000000000000ff", + "logIndex": 0, + "blockHash": "0x19e89674081d676bfe8e6d4f611299347cfdb923db79beff1749838ea5053c37" + } + ], + "blockNumber": 104860903, + "cumulativeGasUsed": "2869841", + "status": 1, + "byzantium": true + }, + "args": [ + "0x3cD69251D04A28d887Ac14cbe2E14c52F3D57823", + "0x2E7222e51c0f6e98610A1543Aa3836E092CDe62c", + "0x5fFbE5302BadED40941A403228E6AD03f93752d9" + ], + "numDeployments": 1, + "solcInputHash": "b08018e2eab5f4ca5e3ba0c1798444d8", + "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract ResilientOracleInterface\",\"name\":\"_resilientOracle\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nativeMarketAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"vaiAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"lastProtectionTriggeredAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"}],\"name\":\"CooldownNotElapsed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidArrayLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"action\",\"type\":\"uint8\"}],\"name\":\"InvalidKeeperAction\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"newMax\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"currentSpot\",\"type\":\"uint256\"}],\"name\":\"InvalidMaxPrice\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"newMin\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"currentSpot\",\"type\":\"uint256\"}],\"name\":\"InvalidMinPrice\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"resetThreshold\",\"type\":\"uint256\"}],\"name\":\"InvalidResetThreshold\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"MarketAlreadyInitialized\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"MarketNotInitialized\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"price\",\"type\":\"uint256\"}],\"name\":\"PriceExceedsUint128\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"currentRangeRatio\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"resetThreshold\",\"type\":\"uint256\"}],\"name\":\"PriceRangeNotConverged\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"ProtectedPriceActive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"ProtectedPriceInactive\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maximum\",\"type\":\"uint256\"}],\"name\":\"ThresholdAboveMaximum\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minimum\",\"type\":\"uint256\"}],\"name\":\"ThresholdBelowMinimum\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"calledContract\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"methodSignature\",\"type\":\"string\"}],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"VAINotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroPriceNotAllowed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroValueNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"whitelisted\",\"type\":\"bool\"}],\"name\":\"BoundedPricingWhitelistUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"oldEnabled\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"newEnabled\",\"type\":\"bool\"}],\"name\":\"CachingEnabledUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"oldCooldown\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"newCooldown\",\"type\":\"uint64\"}],\"name\":\"CooldownPeriodSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"oldMax\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"newMax\",\"type\":\"uint128\"}],\"name\":\"MaxPriceUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"oldMin\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"newMin\",\"type\":\"uint128\"}],\"name\":\"MinPriceUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldAccessControlManager\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAccessControlManager\",\"type\":\"address\"}],\"name\":\"NewAccessControlManager\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"minPrice\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"maxPrice\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"triggerThreshold\",\"type\":\"uint256\"}],\"name\":\"ProtectionInitialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"ProtectionModeExited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"spotPrice\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"minPrice\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"maxPrice\",\"type\":\"uint128\"}],\"name\":\"ProtectionTriggered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldExitThreshold\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newExitThreshold\",\"type\":\"uint256\"}],\"name\":\"ResetThresholdSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldThreshold\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newThreshold\",\"type\":\"uint256\"}],\"name\":\"TriggerThresholdSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"COLLATERAL_PRICE_CACHE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DEBT_PRICE_CACHE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"KEEPER_DEADBAND\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_THRESHOLD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MIN_THRESHOLD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"NATIVE_TOKEN_ADDR\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RESILIENT_ORACLE\",\"outputs\":[{\"internalType\":\"contract ResilientOracleInterface\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"accessControlManager\",\"outputs\":[{\"internalType\":\"contract IAccessControlManagerV8\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"allAssets\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"assetProtectionConfig\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"minPrice\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"maxPrice\",\"type\":\"uint128\"},{\"internalType\":\"bool\",\"name\":\"currentlyUsingProtectedPrice\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"isBoundedPricingEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"lastProtectionTriggeredAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"triggerThreshold\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"resetThreshold\",\"type\":\"uint128\"},{\"internalType\":\"bool\",\"name\":\"cachingEnabled\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"canExitProtection\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"assets\",\"type\":\"address[]\"},{\"internalType\":\"uint128[]\",\"name\":\"proposedMins\",\"type\":\"uint128[]\"},{\"internalType\":\"uint128[]\",\"name\":\"proposedMaxs\",\"type\":\"uint128[]\"}],\"name\":\"checkAndGetWindowDrift\",\"outputs\":[{\"internalType\":\"bool[]\",\"name\":\"needsMinUpdate\",\"type\":\"bool[]\"},{\"internalType\":\"bool[]\",\"name\":\"needsMaxUpdate\",\"type\":\"bool[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"currentlyUsingProtectedPrice\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"exitProtectionMode\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllBoundedPricingEnabledAssets\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedCollateralPrice\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"collateralPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedCollateralPriceView\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"collateralPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedDebtPrice\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debtPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedDebtPriceView\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"debtPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedPrices\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"collateralPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"debtPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"getBoundedPricesView\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"collateralPrice\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"debtPrice\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getInitializedAssets\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"accessControlManager_\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"isBoundedPricingEnabled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nativeMarket\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"accessControlManager_\",\"type\":\"address\"}],\"name\":\"setAccessControlManager\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"}],\"name\":\"setAssetBoundedPricingEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"}],\"name\":\"setCachingEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"newCooldown\",\"type\":\"uint64\"}],\"name\":\"setCooldownPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newTriggerThreshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"newResetThreshold\",\"type\":\"uint256\"}],\"name\":\"setThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"triggerThreshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"resetThreshold\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"enableBoundedPricing\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"enableCaching\",\"type\":\"bool\"}],\"internalType\":\"struct IDeviationBoundedOracle.TokenConfigInput\",\"name\":\"tokenConfig_\",\"type\":\"tuple\"}],\"name\":\"setTokenConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"cooldownPeriod\",\"type\":\"uint64\"},{\"internalType\":\"uint256\",\"name\":\"triggerThreshold\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"resetThreshold\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"enableBoundedPricing\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"enableCaching\",\"type\":\"bool\"}],\"internalType\":\"struct IDeviationBoundedOracle.TokenConfigInput[]\",\"name\":\"tokenConfigs_\",\"type\":\"tuple[]\"}],\"name\":\"setTokenConfigs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"enum IDeviationBoundedOracle.KeeperAction\",\"name\":\"action\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"internalType\":\"struct IDeviationBoundedOracle.KeeperActionItem[]\",\"name\":\"actions\",\"type\":\"tuple[]\"}],\"name\":\"syncPriceBoundsAndProtections\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"newMax\",\"type\":\"uint128\"}],\"name\":\"updateMaxPrice\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"newMin\",\"type\":\"uint128\"}],\"name\":\"updateMinPrice\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"vToken\",\"type\":\"address\"}],\"name\":\"updateProtectionState\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vai\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Venus\",\"events\":{\"Initialized(uint8)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"}},\"kind\":\"dev\",\"methods\":{\"acceptOwnership()\":{\"details\":\"The new owner accepts the ownership transfer.\"},\"canExitProtection(address)\":{\"details\":\"Returns true when both conditions are met: 1. Cooldown period has elapsed since last trigger 2. Price range has converged below exit threshold\",\"params\":{\"asset\":\"The underlying asset address\"},\"returns\":{\"_0\":\"True if protection can be disabled\"}},\"checkAndGetWindowDrift(address[],uint128[],uint128[])\":{\"custom:error\":\"InvalidArrayLength if the input array lengths do not match\",\"details\":\"Allows the keeper to identify stale windows in a single call, avoiding N individual reads. Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)\",\"params\":{\"assets\":\"Array of asset addresses to check\",\"proposedMaxs\":\"Keeper's off-chain window maximum prices\",\"proposedMins\":\"Keeper's off-chain window minimum prices\"},\"returns\":{\"needsMaxUpdate\":\"Whether maxPrice drift exceeds deadband for each asset\",\"needsMinUpdate\":\"Whether minPrice drift exceeds deadband for each asset\"}},\"constructor\":{\"custom:oz-upgrades-unsafe-allow\":\"constructor\",\"params\":{\"_resilientOracle\":\"Address of the ResilientOracle contract\",\"nativeMarketAddress\":\"The address of a native market (for bsc it would be vBNB address)\",\"vaiAddress\":\"The address of the VAI token, or address(0) if VAI is not deployed on the chain.\"}},\"currentlyUsingProtectedPrice(address)\":{\"params\":{\"asset\":\"The underlying asset address\"},\"returns\":{\"_0\":\"True if the asset is currently using the protected price instead of spot\"}},\"exitProtectionMode(address)\":{\"custom:access\":\"Only authorized monitor/keeper addresses\",\"custom:error\":\"ProtectedPriceInactive if protection is not currently activeCooldownNotElapsed if cooldown period has not elapsedPriceRangeNotConverged if window range is still above exit threshold\",\"custom:event\":\"ProtectionModeExited\",\"details\":\"Called by the keeper/monitor after confirming price has normalised. Enforces two conditions on-chain: 1. Cooldown period has elapsed since the last trigger 2. Price range has converged below the exit threshold\",\"params\":{\"asset\":\"The underlying asset address\"}},\"getAllBoundedPricingEnabledAssets()\":{\"details\":\"Iterates the append-only allAssets array and filters by isBoundedPricingEnabled. Gas-free for off-chain callers.\",\"returns\":{\"_0\":\"result Array of whitelisted asset addresses\"}},\"getBoundedCollateralPrice(address)\":{\"custom:event\":\"MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold\",\"details\":\"Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns the conservative (lower) price when protection is active. Used by keepers or direct callers who want atomic update + read.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"collateralPrice\":\"The bounded collateral price\"}},\"getBoundedCollateralPriceView(address)\":{\"details\":\"Reads from transient cache first when the asset's `cachingEnabled` flag is `true` (populated by a prior updateProtectionState call in the same transaction). Falls back to ResilientOracle on cache miss or when caching is disabled. Returns min(spot, windowMin) when protection is active, spot otherwise.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"collateralPrice\":\"The bounded collateral price\"}},\"getBoundedDebtPrice(address)\":{\"custom:event\":\"MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold\",\"details\":\"Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns the conservative (higher) price when protection is active. Used by keepers or direct callers who want atomic update + read.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"debtPrice\":\"The bounded debt price\"}},\"getBoundedDebtPriceView(address)\":{\"details\":\"Reads from transient cache first when the asset's `cachingEnabled` flag is `true` (populated by a prior updateProtectionState call in the same transaction). Falls back to ResilientOracle on cache miss or when caching is disabled. Returns max(spot, windowMax) when protection is active, spot otherwise.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"debtPrice\":\"The bounded debt price\"}},\"getBoundedPrices(address)\":{\"custom:event\":\"MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold\",\"details\":\"Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns both conservative prices in a single call.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"collateralPrice\":\"The bounded collateral price\",\"debtPrice\":\"The bounded debt price\"}},\"getBoundedPricesView(address)\":{\"details\":\"Reads from transient cache first when the asset's `cachingEnabled` flag is `true`; falls back to ResilientOracle on cache miss or when caching is disabled.\",\"params\":{\"vToken\":\"vToken address\"},\"returns\":{\"collateralPrice\":\"The bounded collateral price\",\"debtPrice\":\"The bounded debt price\"}},\"getInitializedAssets()\":{\"returns\":{\"_0\":\"Array of all initialized asset addresses\"}},\"initialize(address)\":{\"params\":{\"accessControlManager_\":\"Address of the access control manager contract\"}},\"isBoundedPricingEnabled(address)\":{\"params\":{\"asset\":\"The underlying asset address\"},\"returns\":{\"_0\":\"True if the asset is whitelisted\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"pendingOwner()\":{\"details\":\"Returns the address of the pending owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"setAccessControlManager(address)\":{\"custom:access\":\"Only Governance\",\"custom:event\":\"Emits NewAccessControlManager event\",\"details\":\"Admin function to set address of AccessControlManager\",\"params\":{\"accessControlManager_\":\"The new address of the AccessControlManager\"}},\"setAssetBoundedPricingEnabled(address,bool)\":{\"custom:access\":\"Only Governance\",\"custom:error\":\"ProtectedPriceActive if trying to disable an asset while protection is active\",\"custom:event\":\"BoundedPricingWhitelistUpdated\",\"params\":{\"asset\":\"The underlying asset address\",\"enabled\":\"Whether bounded pricing should be enabled for the asset\"}},\"setCachingEnabled(address,bool)\":{\"custom:access\":\"Only Governance\",\"custom:error\":\"MarketNotInitialized if the asset has not been initialized\",\"custom:event\":\"CachingEnabledUpdated\",\"details\":\"When disabled, each view/non-view price call recomputes bounded prices from the live spot instead of reading or writing the transient slots. The initial value is set via the `enableCaching` argument of `setTokenConfig`.\",\"params\":{\"asset\":\"The underlying asset address\",\"enabled\":\"Whether transient caching is enabled for this asset\"}},\"setCooldownPeriod(address,uint64)\":{\"custom:access\":\"Only Governance\",\"custom:event\":\"CooldownPeriodSet\",\"params\":{\"asset\":\"The underlying asset address\",\"newCooldown\":\"The new cooldown period in seconds\"}},\"setThresholds(address,uint256,uint256)\":{\"custom:access\":\"Only Governance\",\"custom:error\":\"ThresholdBelowMinimum if newTriggerThreshold is below 5%ThresholdAboveMaximum if newTriggerThreshold is above 50%InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\",\"custom:event\":\"TriggerThresholdSet if the trigger threshold changedResetThresholdSet if the reset threshold changed\",\"params\":{\"asset\":\"The underlying asset address\",\"newResetThreshold\":\"The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\",\"newTriggerThreshold\":\"The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\"}},\"setTokenConfig((address,uint64,uint256,uint256,bool,bool))\":{\"custom:access\":\"Only Governance\",\"custom:event\":\"ProtectionInitializedBoundedPricingWhitelistUpdated\",\"params\":{\"tokenConfig_\":\"Token config input for the asset\"}},\"setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])\":{\"custom:access\":\"Only Governance\",\"custom:error\":\"InvalidArrayLength if the input array is empty\",\"custom:event\":\"ProtectionInitialized for each assetBoundedPricingWhitelistUpdated for each asset\",\"params\":{\"tokenConfigs_\":\"Array of token config inputs, one per asset\"}},\"syncPriceBoundsAndProtections((address,uint8,uint256)[])\":{\"custom:access\":\"Only authorized keeper addresses\",\"custom:error\":\"InvalidKeeperAction if an item carries an unsupported action enum value\",\"custom:event\":\"MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\",\"details\":\"Each item is processed in array order; any item revert rolls back the whole batch. `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode. Empty `actions` is a no-op success.\",\"params\":{\"actions\":\"The list of keeper actions to apply\"}},\"transferOwnership(address)\":{\"details\":\"Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner.\"},\"updateMaxPrice(address,uint128)\":{\"custom:access\":\"Only authorized keeper addresses\",\"custom:event\":\"MaxPriceUpdated\",\"details\":\"Called by the keeper to push corrected max values from the off-chain sliding window. Constraint: newMax must be at or above the current spot price.\",\"params\":{\"asset\":\"The underlying asset address\",\"newMax\":\"The new maximum price\"}},\"updateMinPrice(address,uint128)\":{\"custom:access\":\"Only authorized keeper addresses\",\"custom:event\":\"MinPriceUpdated\",\"details\":\"Called by the keeper to push corrected min values from the off-chain sliding window. Constraint: newMin must be at or below the current spot price.\",\"params\":{\"asset\":\"The underlying asset address\",\"newMin\":\"The new minimum price\"}},\"updateProtectionState(address)\":{\"custom:event\":\"MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold\",\"details\":\"Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before liquidity calculations). Subsequent calls to getBoundedCollateralPriceView / getBoundedDebtPriceView within the same transaction will read from the transient cache instead of querying ResilientOracle again, keeping those functions as `view` and avoiding redundant oracle calls. The transient cache is only populated when the asset's `cachingEnabled` flag is `true`. When caching is disabled, view price reads fall through to live recomputation. Permissionless: anyone can call this, both for gas optimisation and to ensure every caller in the same transaction reads the correct, up-to-date bounded price.\",\"params\":{\"vToken\":\"vToken address\"}}},\"stateVariables\":{\"COLLATERAL_PRICE_CACHE_SLOT\":{\"details\":\"custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache keccak256(abi.encode(uint256(keccak256(\\\"venus-protocol/oracle/DeviationBoundedOracle/collateralCache\\\")) - 1)) & ~bytes32(uint256(0xff))\"},\"DEBT_PRICE_CACHE_SLOT\":{\"details\":\"custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache keccak256(abi.encode(uint256(keccak256(\\\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\\\")) - 1)) & ~bytes32(uint256(0xff))\"},\"RESILIENT_ORACLE\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"nativeMarket\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"},\"vai\":{\"custom:oz-upgrades-unsafe-allow\":\"state-variable-immutable\"}},\"title\":\"DeviationBoundedOracle\",\"version\":1},\"userdoc\":{\"errors\":{\"CooldownNotElapsed(address,uint64,uint64)\":[{\"notice\":\"Thrown when trying to disable protection before cooldown has elapsed\"}],\"InvalidArrayLength()\":[{\"notice\":\"Thrown when the lengths of the arrays are not equal\"}],\"InvalidKeeperAction(uint8)\":[{\"notice\":\"Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\"}],\"InvalidMaxPrice(address,uint128,uint256)\":[{\"notice\":\"Thrown when keeper tries to set maxPrice below current spot\"}],\"InvalidMinPrice(address,uint128,uint256)\":[{\"notice\":\"Thrown when keeper tries to set minPrice above current spot\"}],\"InvalidResetThreshold(uint256)\":[{\"notice\":\"Thrown when the exit threshold is set at or above the trigger threshold\"}],\"MarketAlreadyInitialized(address)\":[{\"notice\":\"Thrown when trying to initialize an already initialized market\"}],\"MarketNotInitialized(address)\":[{\"notice\":\"Thrown when trying to use or update protection for an asset that has not been initialized\"}],\"PriceExceedsUint128(uint256)\":[{\"notice\":\"Thrown when a price exceeds uint128 max\"}],\"PriceRangeNotConverged(address,uint256,uint256)\":[{\"notice\":\"Thrown when trying to disable protection before price range has converged\"}],\"ProtectedPriceActive(address)\":[{\"notice\":\"Thrown when trying to disable bounded pricing for an asset while protection is active\"}],\"ProtectedPriceInactive(address)\":[{\"notice\":\"Thrown when trying to disable protection that is not active\"}],\"ThresholdAboveMaximum(uint256,uint256)\":[{\"notice\":\"Thrown when threshold is set above the maximum allowed value\"}],\"ThresholdBelowMinimum(uint256,uint256)\":[{\"notice\":\"Thrown when threshold is set below the minimum allowed value\"}],\"Unauthorized(address,address,string)\":[{\"notice\":\"Thrown when the action is prohibited by AccessControlManager\"}],\"VAINotAllowed()\":[{\"notice\":\"Thrown when trying to initialize protection for VAI\"}],\"ZeroAddressNotAllowed()\":[{\"notice\":\"Thrown if the supplied address is a zero address where it is not allowed\"}],\"ZeroPriceNotAllowed()\":[{\"notice\":\"Thrown when a zero price is provided where a non-zero price is required\"}],\"ZeroValueNotAllowed()\":[{\"notice\":\"Thrown if the supplied value is 0 where it is not allowed\"}]},\"events\":{\"BoundedPricingWhitelistUpdated(address,bool)\":{\"notice\":\"Emitted when an asset's whitelist status changes\"},\"CachingEnabledUpdated(address,bool,bool)\":{\"notice\":\"Emitted when the per-asset transient caching flag is toggled\"},\"CooldownPeriodSet(address,uint64,uint64)\":{\"notice\":\"Emitted when the cooldown period is updated for an asset\"},\"MaxPriceUpdated(address,uint128,uint128)\":{\"notice\":\"Emitted when the keeper updates the maximum price for an asset\"},\"MinPriceUpdated(address,uint128,uint128)\":{\"notice\":\"Emitted when the keeper updates the minimum price for an asset\"},\"NewAccessControlManager(address,address)\":{\"notice\":\"Emitted when access control manager contract address is changed\"},\"ProtectionInitialized(address,uint128,uint128,uint64,uint256)\":{\"notice\":\"Emitted when protection is initialized for an asset\"},\"ProtectionModeExited(address)\":{\"notice\":\"Emitted when protection mode is disabled for an asset\"},\"ProtectionTriggered(address,uint256,uint128,uint128)\":{\"notice\":\"Emitted when protection mode is triggered for an asset\"},\"ResetThresholdSet(address,uint256,uint256)\":{\"notice\":\"Emitted when the exit threshold is updated for an asset\"},\"TriggerThresholdSet(address,uint256,uint256)\":{\"notice\":\"Emitted when the entry threshold is updated for an asset\"}},\"kind\":\"user\",\"methods\":{\"COLLATERAL_PRICE_CACHE_SLOT()\":{\"notice\":\"Transient storage slot for caching final collateral prices within a transaction\"},\"DEBT_PRICE_CACHE_SLOT()\":{\"notice\":\"Transient storage slot for caching final debt prices within a transaction\"},\"KEEPER_DEADBAND()\":{\"notice\":\"Keeper deadband threshold (5%) \\u2014 min/max corrections below this are suppressed\"},\"MAX_THRESHOLD()\":{\"notice\":\"Maximum allowed threshold value (50%)\"},\"MIN_THRESHOLD()\":{\"notice\":\"Minimum allowed threshold value (5%) to account for keeper deadband\"},\"NATIVE_TOKEN_ADDR()\":{\"notice\":\"Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc) and can serve as any underlying asset of a market that supports native tokens\"},\"RESILIENT_ORACLE()\":{\"notice\":\"Resilient Oracle used to fetch spot prices\"},\"accessControlManager()\":{\"notice\":\"Returns the address of the access control manager contract\"},\"allAssets(uint256)\":{\"notice\":\"Append-only array of all assets ever initialized, used for enumeration\"},\"assetProtectionConfig(address)\":{\"notice\":\"Per-asset protection state\"},\"canExitProtection(address)\":{\"notice\":\"Checks if protection can be exited for an asset\"},\"checkAndGetWindowDrift(address[],uint128[],uint128[])\":{\"notice\":\"Batch-checks which assets' on-chain min/max have drifted beyond the deadband from the keeper's proposed window values\"},\"constructor\":{\"notice\":\"Constructor for the implementation contract. Sets immutable variables.\"},\"currentlyUsingProtectedPrice(address)\":{\"notice\":\"Checks if the asset is currently using the protected (bounded) price\"},\"exitProtectionMode(address)\":{\"notice\":\"Exits protection mode for a given asset\"},\"getAllBoundedPricingEnabledAssets()\":{\"notice\":\"Returns all currently whitelisted asset addresses\"},\"getBoundedCollateralPrice(address)\":{\"notice\":\"Gets the bounded collateral price for a given vToken, updating protection state\"},\"getBoundedCollateralPriceView(address)\":{\"notice\":\"Gets the bounded collateral price for a given vToken (view variant)\"},\"getBoundedDebtPrice(address)\":{\"notice\":\"Gets the bounded debt price for a given vToken, updating protection state\"},\"getBoundedDebtPriceView(address)\":{\"notice\":\"Gets the bounded debt price for a given vToken (view variant)\"},\"getBoundedPrices(address)\":{\"notice\":\"Gets both the bounded collateral and debt prices for a given vToken, updating protection state\"},\"getBoundedPricesView(address)\":{\"notice\":\"Gets both the bounded collateral and debt prices for a given vToken (view variant)\"},\"getInitializedAssets()\":{\"notice\":\"Returns all asset addresses that have ever been initialized\"},\"initialize(address)\":{\"notice\":\"Initializes the contract admin\"},\"isBoundedPricingEnabled(address)\":{\"notice\":\"Checks if an asset is whitelisted for bounded pricing\"},\"nativeMarket()\":{\"notice\":\"Native market address\"},\"setAccessControlManager(address)\":{\"notice\":\"Sets the address of AccessControlManager\"},\"setAssetBoundedPricingEnabled(address,bool)\":{\"notice\":\"Sets whether an asset is enabled for bounded pricing\"},\"setCachingEnabled(address,bool)\":{\"notice\":\"Toggles transient caching of the bounded (collateral, debt) pair for an asset\"},\"setCooldownPeriod(address,uint64)\":{\"notice\":\"Sets the cooldown period for an asset\"},\"setThresholds(address,uint256,uint256)\":{\"notice\":\"Sets the trigger and reset thresholds for an asset\"},\"setTokenConfig((address,uint64,uint256,uint256,bool,bool))\":{\"notice\":\"Initializes protection for a new asset\"},\"setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])\":{\"notice\":\"Batch-initializes protection for multiple assets in a single transaction\"},\"syncPriceBoundsAndProtections((address,uint8,uint256)[])\":{\"notice\":\"Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\"},\"updateMaxPrice(address,uint128)\":{\"notice\":\"Updates the maximum price in the rolling window for a given asset\"},\"updateMinPrice(address,uint128)\":{\"notice\":\"Updates the minimum price in the rolling window for a given asset\"},\"updateProtectionState(address)\":{\"notice\":\"Fetches the spot price, updates the protection window, and caches the resolved collateral and debt prices in transient storage for the duration of the transaction.\"},\"vai()\":{\"notice\":\"VAI address\"}},\"notice\":\"The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations. It maintains a per-market rolling min/max price window. When the current spot price deviates significantly from the window bounds, protection mode activates automatically and conservative pricing kicks in: - Collateral is valued at min(spot, windowMin) \\u2014 caps collateral value at recent window low - Debt is valued at max(spot, windowMax) \\u2014 floors debt value at recent window high This protects against instantaneous or short-duration price manipulation attacks on low-liquidity collateral tokens. Sustained attacks beyond the window period are expected to be handled by off-chain monitoring systems. The oracle exposes both view and non-view price functions. The non-view variants update the price window and trigger protection. The view variants read stored state only. A transient price cache avoids redundant ResilientOracle calls within the same transaction when updateProtectionState is called before the view price reads.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/DeviationBoundedOracle.sol\":\"DeviationBoundedOracle\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./OwnableUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership} and {acceptOwnership}.\\n *\\n * This module is used through inheritance. It will make available all functions\\n * from parent (Ownable).\\n */\\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\\n address private _pendingOwner;\\n\\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\\n\\n function __Ownable2Step_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable2Step_init_unchained() internal onlyInitializing {\\n }\\n /**\\n * @dev Returns the address of the pending owner.\\n */\\n function pendingOwner() public view virtual returns (address) {\\n return _pendingOwner;\\n }\\n\\n /**\\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual override onlyOwner {\\n _pendingOwner = newOwner;\\n emit OwnershipTransferStarted(owner(), newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual override {\\n delete _pendingOwner;\\n super._transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev The new owner accepts the ownership transfer.\\n */\\n function acceptOwnership() public virtual {\\n address sender = _msgSender();\\n require(pendingOwner() == sender, \\\"Ownable2Step: caller is not the new owner\\\");\\n _transferOwnership(sender);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x9140dabc466abab21b48b72dbda26736b1183a310d0e677d3719d201df026510\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/ContextUpgradeable.sol\\\";\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n function __Ownable_init() internal onlyInitializing {\\n __Ownable_init_unchained();\\n }\\n\\n function __Ownable_init_unchained() internal onlyInitializing {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n}\\n\",\"keccak256\":\"0x359a1ab89b46b9aba7bcad3fb651924baf4893d15153049b9976b0fc9be1358e\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\\n *\\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\\n * reused. This mechanism prevents re-execution of each \\\"step\\\" but allows the creation of new initialization steps in\\n * case an upgrade adds a module that needs to be initialized.\\n *\\n * For example:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```solidity\\n * contract MyToken is ERC20Upgradeable {\\n * function initialize() initializer public {\\n * __ERC20_init(\\\"MyToken\\\", \\\"MTK\\\");\\n * }\\n * }\\n *\\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\\n * function initializeV2() reinitializer(2) public {\\n * __ERC20Permit_init(\\\"MyToken\\\");\\n * }\\n * }\\n * ```\\n *\\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\\n *\\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\\n *\\n * [CAUTION]\\n * ====\\n * Avoid leaving a contract uninitialized.\\n *\\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * /// @custom:oz-upgrades-unsafe-allow constructor\\n * constructor() {\\n * _disableInitializers();\\n * }\\n * ```\\n * ====\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n * @custom:oz-retyped-from bool\\n */\\n uint8 private _initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private _initializing;\\n\\n /**\\n * @dev Triggered when the contract has been initialized or reinitialized.\\n */\\n event Initialized(uint8 version);\\n\\n /**\\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\\n * `onlyInitializing` functions can be used to initialize parent contracts.\\n *\\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\\n * constructor.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier initializer() {\\n bool isTopLevelCall = !_initializing;\\n require(\\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n _initialized = 1;\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(1);\\n }\\n }\\n\\n /**\\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\\n * used to initialize parent contracts.\\n *\\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\\n * are added through upgrades and that require initialization.\\n *\\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\\n * cannot be nested. If one is invoked in the context of another, execution will revert.\\n *\\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\\n * a contract, executing them in the right order is up to the developer or operator.\\n *\\n * WARNING: setting the version to 255 will prevent any future reinitialization.\\n *\\n * Emits an {Initialized} event.\\n */\\n modifier reinitializer(uint8 version) {\\n require(!_initializing && _initialized < version, \\\"Initializable: contract is already initialized\\\");\\n _initialized = version;\\n _initializing = true;\\n _;\\n _initializing = false;\\n emit Initialized(version);\\n }\\n\\n /**\\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\\n */\\n modifier onlyInitializing() {\\n require(_initializing, \\\"Initializable: contract is not initializing\\\");\\n _;\\n }\\n\\n /**\\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\\n * through proxies.\\n *\\n * Emits an {Initialized} event the first time it is successfully executed.\\n */\\n function _disableInitializers() internal virtual {\\n require(!_initializing, \\\"Initializable: contract is initializing\\\");\\n if (_initialized != type(uint8).max) {\\n _initialized = type(uint8).max;\\n emit Initialized(type(uint8).max);\\n }\\n }\\n\\n /**\\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\\n */\\n function _getInitializedVersion() internal view returns (uint8) {\\n return _initialized;\\n }\\n\\n /**\\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\\n */\\n function _isInitializing() internal view returns (bool) {\\n return _initializing;\\n }\\n}\\n\",\"keccak256\":\"0x89be10e757d242e9b18d5a32c9fbe2019f6d63052bbe46397a430a1d60d7f794\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary AddressUpgradeable {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n *\\n * Furthermore, `isContract` will also return true if the target contract within\\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\\n * which only has an effect at the end of a transaction.\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\\n *\\n * _Available since v4.8._\\n */\\n function verifyCallResultFromTarget(\\n address target,\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n if (success) {\\n if (returndata.length == 0) {\\n // only check isContract if the call was successful and the return data is empty\\n // otherwise we already know that it was a contract\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n }\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n /**\\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason or using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n _revert(returndata, errorMessage);\\n }\\n }\\n\\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n /// @solidity memory-safe-assembly\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9c80f545915582e63fe206c6ce27cbe85a86fc10b9cd2a0e8c9488fb7c2ee422\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\nimport {Initializable} from \\\"../proxy/utils/Initializable.sol\\\";\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract ContextUpgradeable is Initializable {\\n function __Context_init() internal onlyInitializing {\\n }\\n\\n function __Context_init_unchained() internal onlyInitializing {\\n }\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[50] private __gap;\\n}\\n\",\"keccak256\":\"0x75097e35253e7fb282ee4d7f27a80eaacfa759923185bf17302a89cbc059c5ef\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/IAccessControl.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev External interface of AccessControl declared to support ERC165 detection.\\n */\\ninterface IAccessControl {\\n /**\\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\\n *\\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\\n * {RoleAdminChanged} not being emitted signaling this.\\n *\\n * _Available since v3.1._\\n */\\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\\n\\n /**\\n * @dev Emitted when `account` is granted `role`.\\n *\\n * `sender` is the account that originated the contract call, an admin role\\n * bearer except when using {AccessControl-_setupRole}.\\n */\\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Emitted when `account` is revoked `role`.\\n *\\n * `sender` is the account that originated the contract call:\\n * - if using `revokeRole`, it is the admin role bearer\\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\\n */\\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\\n\\n /**\\n * @dev Returns `true` if `account` has been granted `role`.\\n */\\n function hasRole(bytes32 role, address account) external view returns (bool);\\n\\n /**\\n * @dev Returns the admin role that controls `role`. See {grantRole} and\\n * {revokeRole}.\\n *\\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\\n */\\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\\n\\n /**\\n * @dev Grants `role` to `account`.\\n *\\n * If `account` had not been already granted `role`, emits a {RoleGranted}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function grantRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from `account`.\\n *\\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\\n *\\n * Requirements:\\n *\\n * - the caller must have ``role``'s admin role.\\n */\\n function revokeRole(bytes32 role, address account) external;\\n\\n /**\\n * @dev Revokes `role` from the calling account.\\n *\\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\\n * purpose is to provide a mechanism for accounts to lose their privileges\\n * if they are compromised (such as when a trusted device is misplaced).\\n *\\n * If the calling account had been granted `role`, emits a {RoleRevoked}\\n * event.\\n *\\n * Requirements:\\n *\\n * - the caller must be `account`.\\n */\\n function renounceRole(bytes32 role, address account) external;\\n}\\n\",\"keccak256\":\"0x59ce320a585d7e1f163cd70390a0ef2ff9cec832e2aa544293a00692465a7a57\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\\n}\\n\",\"keccak256\":\"0x287b55befed2961a7eabd7d7b1b2839cbca8a5b80ef8dcbb25ed3d4c2002c305\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport \\\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\\\";\\nimport \\\"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\\\";\\n\\nimport \\\"./IAccessControlManagerV8.sol\\\";\\n\\n/**\\n * @title AccessControlledV8\\n * @author Venus\\n * @notice This contract is helper between access control manager and actual contract. This contract further inherited by other contract (using solidity 0.8.13)\\n * to integrate access controlled mechanism. It provides initialise methods and verifying access methods.\\n */\\nabstract contract AccessControlledV8 is Initializable, Ownable2StepUpgradeable {\\n /// @notice Access control manager contract\\n IAccessControlManagerV8 internal _accessControlManager;\\n\\n /**\\n * @dev This empty reserved space is put in place to allow future versions to add new\\n * variables without shifting down storage in the inheritance chain.\\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n */\\n uint256[49] private __gap;\\n\\n /// @notice Emitted when access control manager contract address is changed\\n event NewAccessControlManager(address oldAccessControlManager, address newAccessControlManager);\\n\\n /// @notice Thrown when the action is prohibited by AccessControlManager\\n error Unauthorized(address sender, address calledContract, string methodSignature);\\n\\n function __AccessControlled_init(address accessControlManager_) internal onlyInitializing {\\n __Ownable2Step_init();\\n __AccessControlled_init_unchained(accessControlManager_);\\n }\\n\\n function __AccessControlled_init_unchained(address accessControlManager_) internal onlyInitializing {\\n _setAccessControlManager(accessControlManager_);\\n }\\n\\n /**\\n * @notice Sets the address of AccessControlManager\\n * @dev Admin function to set address of AccessControlManager\\n * @param accessControlManager_ The new address of the AccessControlManager\\n * @custom:event Emits NewAccessControlManager event\\n * @custom:access Only Governance\\n */\\n function setAccessControlManager(address accessControlManager_) external onlyOwner {\\n _setAccessControlManager(accessControlManager_);\\n }\\n\\n /**\\n * @notice Returns the address of the access control manager contract\\n */\\n function accessControlManager() external view returns (IAccessControlManagerV8) {\\n return _accessControlManager;\\n }\\n\\n /**\\n * @dev Internal function to set address of AccessControlManager\\n * @param accessControlManager_ The new address of the AccessControlManager\\n */\\n function _setAccessControlManager(address accessControlManager_) internal {\\n require(address(accessControlManager_) != address(0), \\\"invalid acess control manager address\\\");\\n address oldAccessControlManager = address(_accessControlManager);\\n _accessControlManager = IAccessControlManagerV8(accessControlManager_);\\n emit NewAccessControlManager(oldAccessControlManager, accessControlManager_);\\n }\\n\\n /**\\n * @notice Reverts if the call is not allowed by AccessControlManager\\n * @param signature Method signature\\n */\\n function _checkAccessAllowed(string memory signature) internal view {\\n bool isAllowedToCall = _accessControlManager.isAllowedToCall(msg.sender, signature);\\n\\n if (!isAllowedToCall) {\\n revert Unauthorized(msg.sender, address(this), signature);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfe0fd441c84ac907cabc88db69ef04f6d7532d770c7e6a1dfe6e7d6305debb49\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport \\\"@openzeppelin/contracts/access/IAccessControl.sol\\\";\\n\\n/**\\n * @title IAccessControlManagerV8\\n * @author Venus\\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\\n */\\ninterface IAccessControlManagerV8 is IAccessControl {\\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\\n\\n function revokeCallPermission(\\n address contractAddress,\\n string calldata functionSig,\\n address accountToRevoke\\n ) external;\\n\\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\\n\\n function hasPermission(\\n address account,\\n address contractAddress,\\n string calldata functionSig\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xaa29b098440d0b3a131c5ecdf25ce548790c1b5ac7bf9b5c0264b6af6f7a1e0b\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/constants.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\\nuint256 constant EXP_SCALE = 1e18;\\n\\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\\nuint256 constant MANTISSA_ONE = EXP_SCALE;\\n\\n/// @dev The approximate number of seconds per year\\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\\n\",\"keccak256\":\"0x14de93ead464da249af31bea0e3bcfb62ec693bea3475fb4d90f055ac81dc5eb\",\"license\":\"BSD-3-Clause\"},\"@venusprotocol/solidity-utilities/contracts/validators.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\\nerror ZeroAddressNotAllowed();\\n\\n/// @notice Thrown if the supplied value is 0 where it is not allowed\\nerror ZeroValueNotAllowed();\\n\\n/// @notice Checks if the provided address is nonzero, reverts otherwise\\n/// @param address_ Address to check\\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\\nfunction ensureNonzeroAddress(address address_) pure {\\n if (address_ == address(0)) {\\n revert ZeroAddressNotAllowed();\\n }\\n}\\n\\n/// @notice Checks if the provided value is nonzero, reverts otherwise\\n/// @param value_ Value to check\\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\\nfunction ensureNonzeroValue(uint256 value_) pure {\\n if (value_ == 0) {\\n revert ZeroValueNotAllowed();\\n }\\n}\\n\",\"keccak256\":\"0xdb88e14d50dd21889ca3329d755673d022c47e8da005b6a545c7f69c2c4b7b86\",\"license\":\"BSD-3-Clause\"},\"contracts/DeviationBoundedOracle.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\nimport { VBep20Interface } from \\\"./interfaces/VBep20Interface.sol\\\";\\nimport { ResilientOracleInterface } from \\\"./interfaces/OracleInterface.sol\\\";\\nimport { IDeviationBoundedOracle } from \\\"./interfaces/IDeviationBoundedOracle.sol\\\";\\nimport { AccessControlledV8 } from \\\"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\\\";\\nimport { EXP_SCALE } from \\\"@venusprotocol/solidity-utilities/contracts/constants.sol\\\";\\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \\\"@venusprotocol/solidity-utilities/contracts/validators.sol\\\";\\nimport { Transient } from \\\"./lib/Transient.sol\\\";\\n\\n/**\\n * @title DeviationBoundedOracle\\n * @author Venus\\n * @notice The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations.\\n *\\n * It maintains a per-market rolling min/max price window. When the current spot price deviates\\n * significantly from the window bounds, protection mode activates automatically and conservative\\n * pricing kicks in:\\n * - Collateral is valued at min(spot, windowMin) \\u2014 caps collateral value at recent window low\\n * - Debt is valued at max(spot, windowMax) \\u2014 floors debt value at recent window high\\n *\\n * This protects against instantaneous or short-duration price manipulation attacks on low-liquidity\\n * collateral tokens. Sustained attacks beyond the window period are expected to be handled by\\n * off-chain monitoring systems.\\n *\\n * The oracle exposes both view and non-view price functions. The non-view variants update the\\n * price window and trigger protection. The view variants read stored state only. A transient\\n * price cache avoids redundant ResilientOracle calls within the same transaction when\\n * updateProtectionState is called before the view price reads.\\n */\\ncontract DeviationBoundedOracle is AccessControlledV8, IDeviationBoundedOracle {\\n /// @notice Minimum allowed threshold value (5%) to account for keeper deadband\\n uint256 public constant MIN_THRESHOLD = 5e16;\\n\\n /// @notice Maximum allowed threshold value (50%)\\n uint256 public constant MAX_THRESHOLD = 50e16;\\n\\n /// @notice Keeper deadband threshold (5%) \\u2014 min/max corrections below this are suppressed\\n uint256 public constant KEEPER_DEADBAND = 5e16;\\n\\n /// @notice Resilient Oracle used to fetch spot prices\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\\n\\n /// @notice Native market address\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n address public immutable nativeMarket;\\n\\n /// @notice VAI address\\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\\n address public immutable vai;\\n\\n /// @notice Transient storage slot for caching final collateral prices within a transaction\\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache\\n /// keccak256(abi.encode(uint256(keccak256(\\\"venus-protocol/oracle/DeviationBoundedOracle/collateralCache\\\")) - 1))\\n /// & ~bytes32(uint256(0xff))\\n bytes32 public constant COLLATERAL_PRICE_CACHE_SLOT =\\n 0x7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf7000;\\n\\n /// @notice Transient storage slot for caching final debt prices within a transaction\\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache\\n /// keccak256(abi.encode(uint256(keccak256(\\\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\\\")) - 1))\\n /// & ~bytes32(uint256(0xff))\\n bytes32 public constant DEBT_PRICE_CACHE_SLOT = 0x84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb8970600;\\n\\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\\n /// and can serve as any underlying asset of a market that supports native tokens\\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\\n\\n /// @notice Per-asset protection state\\n mapping(address => MarketProtectionState) public assetProtectionConfig;\\n\\n /// @notice Append-only array of all assets ever initialized, used for enumeration\\n address[] public allAssets;\\n\\n /// @notice Storage gap for upgrades\\n uint256[48] private __gap;\\n\\n /**\\n * @notice Constructor for the implementation contract. Sets immutable variables.\\n * @param _resilientOracle Address of the ResilientOracle contract\\n * @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\\n * @param vaiAddress The address of the VAI token, or address(0) if VAI is not deployed on the chain.\\n * @custom:oz-upgrades-unsafe-allow constructor\\n */\\n constructor(ResilientOracleInterface _resilientOracle, address nativeMarketAddress, address vaiAddress) {\\n ensureNonzeroAddress(address(_resilientOracle));\\n ensureNonzeroAddress(nativeMarketAddress);\\n RESILIENT_ORACLE = _resilientOracle;\\n nativeMarket = nativeMarketAddress;\\n vai = vaiAddress;\\n _disableInitializers();\\n }\\n\\n /**\\n * @notice Initializes the contract admin\\n * @param accessControlManager_ Address of the access control manager contract\\n */\\n function initialize(address accessControlManager_) external initializer {\\n __AccessControlled_init(accessControlManager_);\\n }\\n\\n // ----- Non-view price functions (update window + trigger protection) -----\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\\n * and returns the conservative (lower) price when protection is active.\\n * Used by keepers or direct callers who want atomic update + read.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice) {\\n (collateralPrice, ) = _updateAndGetBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken, updating protection state\\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\\n * and returns the conservative (higher) price when protection is active.\\n * Used by keepers or direct callers who want atomic update + read.\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice) {\\n (, debtPrice) = _updateAndGetBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\\n * and returns both conservative prices in a single call.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice) {\\n return _updateAndGetBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Fetches the spot price, updates the protection window, and caches the resolved\\n * collateral and debt prices in transient storage for the duration of the transaction.\\n * @dev Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before\\n * liquidity calculations). Subsequent calls to getBoundedCollateralPriceView /\\n * getBoundedDebtPriceView within the same transaction will read from the transient cache\\n * instead of querying ResilientOracle again, keeping those functions as `view` and\\n * avoiding redundant oracle calls.\\n * The transient cache is only populated when the asset's `cachingEnabled` flag is `true`.\\n * When caching is disabled, view price reads fall through to live recomputation.\\n * Permissionless: anyone can call this, both for gas optimisation and to ensure every\\n * caller in the same transaction reads the correct, up-to-date bounded price.\\n * @param vToken vToken address\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function updateProtectionState(address vToken) external {\\n _updateAndGetBoundedPrices(vToken);\\n }\\n\\n // ----- View price functions (read stored/cached state only) -----\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`\\n * (populated by a prior updateProtectionState call in the same transaction). Falls back\\n * to ResilientOracle on cache miss or when caching is disabled.\\n * Returns min(spot, windowMin) when protection is active, spot otherwise.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n */\\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 collateralPrice) {\\n (collateralPrice, ) = _computeBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`\\n * (populated by a prior updateProtectionState call in the same transaction). Falls back\\n * to ResilientOracle on cache miss or when caching is disabled.\\n * Returns max(spot, windowMax) when protection is active, spot otherwise.\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n */\\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 debtPrice) {\\n (, debtPrice) = _computeBoundedPrices(vToken);\\n }\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n */\\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice) {\\n return _computeBoundedPrices(vToken);\\n }\\n\\n // ----- Keeper functions -----\\n\\n /**\\n * @notice Updates the minimum price in the rolling window for a given asset\\n * @dev Called by the keeper to push corrected min values from the off-chain sliding window.\\n * Constraint: newMin must be at or below the current spot price.\\n * @param asset The underlying asset address\\n * @param newMin The new minimum price\\n * @custom:access Only authorized keeper addresses\\n * @custom:event MinPriceUpdated\\n */\\n function updateMinPrice(address asset, uint128 newMin) external {\\n _checkAccessAllowed(\\\"updateMinPrice(address,uint128)\\\");\\n _validateAndUpdateBound(asset, newMin, PriceBoundType.MIN);\\n }\\n\\n /**\\n * @notice Updates the maximum price in the rolling window for a given asset\\n * @dev Called by the keeper to push corrected max values from the off-chain sliding window.\\n * Constraint: newMax must be at or above the current spot price.\\n * @param asset The underlying asset address\\n * @param newMax The new maximum price\\n * @custom:access Only authorized keeper addresses\\n * @custom:event MaxPriceUpdated\\n */\\n function updateMaxPrice(address asset, uint128 newMax) external {\\n _checkAccessAllowed(\\\"updateMaxPrice(address,uint128)\\\");\\n _validateAndUpdateBound(asset, newMax, PriceBoundType.MAX);\\n }\\n\\n /**\\n * @notice Exits protection mode for a given asset\\n * @dev Called by the keeper/monitor after confirming price has normalised.\\n * Enforces two conditions on-chain:\\n * 1. Cooldown period has elapsed since the last trigger\\n * 2. Price range has converged below the exit threshold\\n * @param asset The underlying asset address\\n * @custom:access Only authorized monitor/keeper addresses\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\\n * @custom:error PriceRangeNotConverged if window range is still above exit threshold\\n * @custom:event ProtectionModeExited\\n */\\n function exitProtectionMode(address asset) external {\\n _checkAccessAllowed(\\\"exitProtectionMode(address)\\\");\\n _exitProtectionMode(asset);\\n }\\n\\n /**\\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\\n * Empty `actions` is a no-op success.\\n * @param actions The list of keeper actions to apply\\n * @custom:access Only authorized keeper addresses\\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\\n */\\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external {\\n _checkAccessAllowed(\\\"syncPriceBoundsAndProtections((address,uint8,uint256)[])\\\");\\n uint256 len = actions.length;\\n for (uint256 i; i < len; ++i) {\\n KeeperActionItem calldata item = actions[i];\\n if (item.action == KeeperAction.SetMinPrice) {\\n _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MIN);\\n } else if (item.action == KeeperAction.SetMaxPrice) {\\n _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MAX);\\n } else if (item.action == KeeperAction.ExitProtectionMode) {\\n _exitProtectionMode(item.asset);\\n } else {\\n revert InvalidKeeperAction(uint8(item.action));\\n }\\n }\\n }\\n\\n // ----- Admin functions (governance-gated) -----\\n\\n /**\\n * @notice Initializes protection for a new asset\\n * @param tokenConfig_ Token config input for the asset\\n * @custom:access Only Governance\\n * @custom:event ProtectionInitialized\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external {\\n _checkAccessAllowed(\\\"setTokenConfig((address,uint64,uint256,uint256,bool,bool))\\\");\\n _setTokenConfig(\\n tokenConfig_.asset,\\n tokenConfig_.cooldownPeriod,\\n tokenConfig_.triggerThreshold,\\n tokenConfig_.resetThreshold,\\n tokenConfig_.enableBoundedPricing,\\n tokenConfig_.enableCaching\\n );\\n }\\n\\n /**\\n * @notice Batch-initializes protection for multiple assets in a single transaction\\n * @param tokenConfigs_ Array of token config inputs, one per asset\\n * @custom:access Only Governance\\n * @custom:error InvalidArrayLength if the input array is empty\\n * @custom:event ProtectionInitialized for each asset\\n * @custom:event BoundedPricingWhitelistUpdated for each asset\\n */\\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external {\\n _checkAccessAllowed(\\\"setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])\\\");\\n uint256 len = tokenConfigs_.length;\\n if (len == 0) revert InvalidArrayLength();\\n\\n for (uint256 i; i < len; ++i) {\\n TokenConfigInput calldata tokenConfig = tokenConfigs_[i];\\n _setTokenConfig(\\n tokenConfig.asset,\\n tokenConfig.cooldownPeriod,\\n tokenConfig.triggerThreshold,\\n tokenConfig.resetThreshold,\\n tokenConfig.enableBoundedPricing,\\n tokenConfig.enableCaching\\n );\\n }\\n }\\n\\n /**\\n * @notice Sets the cooldown period for an asset\\n * @param asset The underlying asset address\\n * @param newCooldown The new cooldown period in seconds\\n * @custom:access Only Governance\\n * @custom:event CooldownPeriodSet\\n */\\n function setCooldownPeriod(address asset, uint64 newCooldown) external {\\n _checkAccessAllowed(\\\"setCooldownPeriod(address,uint64)\\\");\\n ensureNonzeroAddress(asset);\\n ensureNonzeroValue(newCooldown);\\n\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n emit CooldownPeriodSet(asset, state.cooldownPeriod, newCooldown);\\n state.cooldownPeriod = newCooldown;\\n }\\n\\n /**\\n * @notice Sets the trigger and reset thresholds for an asset\\n * @param asset The underlying asset address\\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\\n * @custom:access Only Governance\\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\\n * @custom:event TriggerThresholdSet if the trigger threshold changed\\n * @custom:event ResetThresholdSet if the reset threshold changed\\n */\\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external {\\n _checkAccessAllowed(\\\"setThresholds(address,uint256,uint256)\\\");\\n ensureNonzeroAddress(asset);\\n ensureNonzeroValue(newTriggerThreshold);\\n ensureNonzeroValue(newResetThreshold);\\n if (newTriggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(newTriggerThreshold, MIN_THRESHOLD);\\n if (newTriggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(newTriggerThreshold, MAX_THRESHOLD);\\n if (newResetThreshold >= newTriggerThreshold) revert InvalidResetThreshold(newResetThreshold);\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n\\n if (newTriggerThreshold != state.triggerThreshold) {\\n emit TriggerThresholdSet(asset, state.triggerThreshold, newTriggerThreshold);\\n state.triggerThreshold = uint128(newTriggerThreshold);\\n }\\n if (newResetThreshold != state.resetThreshold) {\\n emit ResetThresholdSet(asset, state.resetThreshold, newResetThreshold);\\n state.resetThreshold = uint128(newResetThreshold);\\n }\\n }\\n\\n /**\\n * @notice Sets whether an asset is enabled for bounded pricing\\n * @param asset The underlying asset address\\n * @param enabled Whether bounded pricing should be enabled for the asset\\n * @custom:access Only Governance\\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external {\\n _checkAccessAllowed(\\\"setAssetBoundedPricingEnabled(address,bool)\\\");\\n ensureNonzeroAddress(asset);\\n\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n\\n if (!enabled && state.currentlyUsingProtectedPrice) {\\n revert ProtectedPriceActive(asset);\\n }\\n\\n if (state.isBoundedPricingEnabled == enabled) return;\\n\\n // reset the window if re-enabling\\n if (enabled) {\\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\\n _setMinPrice(state, asset, spotU128);\\n _setMaxPrice(state, asset, spotU128);\\n }\\n\\n state.isBoundedPricingEnabled = enabled;\\n emit BoundedPricingWhitelistUpdated(asset, enabled);\\n }\\n\\n /**\\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\\n * live spot instead of reading or writing the transient slots. The initial value is\\n * set via the `enableCaching` argument of `setTokenConfig`.\\n * @param asset The underlying asset address\\n * @param enabled Whether transient caching is enabled for this asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CachingEnabledUpdated\\n */\\n function setCachingEnabled(address asset, bool enabled) external {\\n _checkAccessAllowed(\\\"setCachingEnabled(address,bool)\\\");\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n emit CachingEnabledUpdated(asset, state.cachingEnabled, enabled);\\n state.cachingEnabled = enabled;\\n }\\n\\n // ----- View helpers -----\\n\\n /**\\n * @notice Returns all asset addresses that have ever been initialized\\n * @return Array of all initialized asset addresses\\n */\\n function getInitializedAssets() external view returns (address[] memory) {\\n return allAssets;\\n }\\n\\n /**\\n * @notice Checks if an asset is whitelisted for bounded pricing\\n * @param asset The underlying asset address\\n * @return True if the asset is whitelisted\\n */\\n function isBoundedPricingEnabled(address asset) external view returns (bool) {\\n return assetProtectionConfig[asset].isBoundedPricingEnabled;\\n }\\n\\n /**\\n * @notice Checks if the asset is currently using the protected (bounded) price\\n * @param asset The underlying asset address\\n * @return True if the asset is currently using the protected price instead of spot\\n */\\n function currentlyUsingProtectedPrice(address asset) external view returns (bool) {\\n return assetProtectionConfig[asset].currentlyUsingProtectedPrice;\\n }\\n\\n /**\\n * @notice Returns all currently whitelisted asset addresses\\n * @dev Iterates the append-only allAssets array and filters by isBoundedPricingEnabled.\\n * Gas-free for off-chain callers.\\n * @return result Array of whitelisted asset addresses\\n */\\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory) {\\n uint256 len = allAssets.length;\\n address[] memory temp = new address[](len);\\n uint256 count;\\n for (uint256 i; i < len; ++i) {\\n if (assetProtectionConfig[allAssets[i]].isBoundedPricingEnabled) {\\n temp[count++] = allAssets[i];\\n }\\n }\\n address[] memory result = new address[](count);\\n for (uint256 i; i < count; ++i) {\\n result[i] = temp[i];\\n }\\n return result;\\n }\\n\\n /**\\n * @notice Checks if protection can be exited for an asset\\n * @dev Returns true when both conditions are met:\\n * 1. Cooldown period has elapsed since last trigger\\n * 2. Price range has converged below exit threshold\\n * @param asset The underlying asset address\\n * @return True if protection can be disabled\\n */\\n function canExitProtection(address asset) external view returns (bool) {\\n MarketProtectionState storage state = assetProtectionConfig[asset];\\n return\\n state.currentlyUsingProtectedPrice &&\\n block.timestamp >= uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod) &&\\n _computePriceBoundRatio(state.minPrice, state.maxPrice) < state.resetThreshold;\\n }\\n\\n /**\\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the deadband\\n * from the keeper's proposed window values\\n * @dev Allows the keeper to identify stale windows in a single call, avoiding N individual reads.\\n * Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)\\n * @param assets Array of asset addresses to check\\n * @param proposedMins Keeper's off-chain window minimum prices\\n * @param proposedMaxs Keeper's off-chain window maximum prices\\n * @return needsMinUpdate Whether minPrice drift exceeds deadband for each asset\\n * @return needsMaxUpdate Whether maxPrice drift exceeds deadband for each asset\\n * @custom:error InvalidArrayLength if the input array lengths do not match\\n */\\n function checkAndGetWindowDrift(\\n address[] calldata assets,\\n uint128[] calldata proposedMins,\\n uint128[] calldata proposedMaxs\\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate) {\\n uint256 len = assets.length;\\n if (len != proposedMins.length || len != proposedMaxs.length) revert InvalidArrayLength();\\n\\n needsMinUpdate = new bool[](len);\\n needsMaxUpdate = new bool[](len);\\n\\n for (uint256 i; i < len; ++i) {\\n MarketProtectionState storage state = assetProtectionConfig[assets[i]];\\n needsMinUpdate[i] = _exceedsCorrectionDeadband(state.minPrice, proposedMins[i]);\\n needsMaxUpdate[i] = _exceedsCorrectionDeadband(state.maxPrice, proposedMaxs[i]);\\n }\\n }\\n\\n // ----- Internal functions -----\\n\\n /**\\n * @notice Initializes protection parameters and price window for a single asset\\n * @dev Fetches the current spot price from ResilientOracle to seed the initial min/max window,\\n * confirming the oracle is live for this asset before it is listed. Both bounds start at\\n * spot so the window expands naturally as prices move. Can only be called once per asset.\\n * @param asset The underlying asset address\\n * @param cooldownPeriod Minimum time protection stays active after last trigger\\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\\n * @param enableBoundedPricing Whether to enable bounded pricing immediately upon initialization\\n * @param enableCaching Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\\n * @custom:error VAINotAllowed if asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n */\\n function _setTokenConfig(\\n address asset,\\n uint64 cooldownPeriod,\\n uint256 triggerThreshold,\\n uint256 resetThreshold,\\n bool enableBoundedPricing,\\n bool enableCaching\\n ) internal {\\n ensureNonzeroAddress(asset);\\n ensureNonzeroValue(cooldownPeriod);\\n ensureNonzeroValue(triggerThreshold);\\n ensureNonzeroValue(resetThreshold);\\n if (assetProtectionConfig[asset].asset != address(0)) revert MarketAlreadyInitialized(asset);\\n if (triggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(triggerThreshold, MIN_THRESHOLD);\\n if (triggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(triggerThreshold, MAX_THRESHOLD);\\n if (resetThreshold >= triggerThreshold) revert InvalidResetThreshold(resetThreshold);\\n if (asset == vai) revert VAINotAllowed();\\n\\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\\n\\n assetProtectionConfig[asset] = MarketProtectionState({\\n minPrice: spotU128,\\n maxPrice: spotU128,\\n currentlyUsingProtectedPrice: false,\\n isBoundedPricingEnabled: enableBoundedPricing,\\n lastProtectionTriggeredAt: 0,\\n cooldownPeriod: cooldownPeriod,\\n asset: asset,\\n triggerThreshold: uint128(triggerThreshold),\\n resetThreshold: uint128(resetThreshold),\\n cachingEnabled: enableCaching\\n });\\n\\n allAssets.push(asset);\\n\\n emit ProtectionInitialized(asset, spotU128, spotU128, cooldownPeriod, triggerThreshold);\\n emit BoundedPricingWhitelistUpdated(asset, enableBoundedPricing);\\n }\\n\\n /**\\n * @notice Validates and applies a keeper-provided min or max price update\\n * @param asset The underlying asset address\\n * @param newPrice The new price value to set\\n * @param boundType Whether this is a MIN or MAX bound update\\n * @custom:error ZeroPriceNotAllowed if newPrice is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMinPrice if boundType is MIN and newPrice exceeds the current spot or is strictly above maxPrice\\n * @custom:error InvalidMaxPrice if boundType is MAX and newPrice is below the current spot or is strictly below minPrice\\n */\\n function _validateAndUpdateBound(address asset, uint128 newPrice, PriceBoundType boundType) internal {\\n ensureNonzeroAddress(asset);\\n if (newPrice == 0) revert ZeroPriceNotAllowed();\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n\\n uint256 currentSpot = _fetchSpotPrice(asset);\\n if (boundType == PriceBoundType.MIN) {\\n if (newPrice > state.maxPrice || uint256(newPrice) > currentSpot)\\n revert InvalidMinPrice(asset, newPrice, currentSpot);\\n _setMinPrice(state, asset, newPrice);\\n } else if (boundType == PriceBoundType.MAX) {\\n if (newPrice < state.minPrice || uint256(newPrice) < currentSpot)\\n revert InvalidMaxPrice(asset, newPrice, currentSpot);\\n _setMaxPrice(state, asset, newPrice);\\n }\\n }\\n\\n /**\\n * @notice Clears protection for an asset once cooldown has elapsed and the window has converged\\n * @dev Shared body of `exitProtectionMode` and the ExitProtectionMode branch of `syncPriceBoundsAndProtections`.\\n * Callers are responsible for ACM gating before invoking this helper.\\n * @param asset The underlying asset address\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\\n */\\n function _exitProtectionMode(address asset) internal {\\n ensureNonzeroAddress(asset);\\n MarketProtectionState storage state = _ensureInitialized(asset);\\n\\n if (!state.currentlyUsingProtectedPrice) revert ProtectedPriceInactive(asset);\\n\\n if (block.timestamp < uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod)) {\\n revert CooldownNotElapsed(asset, state.lastProtectionTriggeredAt, state.cooldownPeriod);\\n }\\n\\n uint256 rangeRatio = _computePriceBoundRatio(state.minPrice, state.maxPrice);\\n if (rangeRatio >= state.resetThreshold) {\\n revert PriceRangeNotConverged(asset, rangeRatio, state.resetThreshold);\\n }\\n\\n state.currentlyUsingProtectedPrice = false;\\n state.lastProtectionTriggeredAt = 0;\\n emit ProtectionModeExited(asset);\\n }\\n\\n /**\\n * @notice Shared non-view logic for all bounded price functions.\\n * Fetches spot, updates window, triggers protection if needed, and returns both bounded prices.\\n * @param vToken vToken address\\n * @return minPrice The bounded lower (collateral) price\\n * @return maxPrice The bounded upper (debt) price\\n */\\n function _updateAndGetBoundedPrices(address vToken) internal returns (uint256 minPrice, uint256 maxPrice) {\\n address asset = _getUnderlyingAsset(vToken);\\n\\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\\n (minPrice, maxPrice) = _getCachedPrices(asset);\\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\\n\\n // return early if failure from resilient oracle to prevent cold SLOAD\\n uint256 spot = _fetchSpotPrice(asset);\\n MarketProtectionState storage state = assetProtectionConfig[asset];\\n if (!state.isBoundedPricingEnabled) {\\n _setCachedPrices(asset, spot, spot);\\n return (spot, spot);\\n }\\n (uint128 updatedMin, uint128 updatedMax, bool windowExpanded) = _expandPriceWindow(state, spot, asset);\\n bool protectionActive = _checkAndTriggerProtection(state, spot, asset, windowExpanded);\\n (minPrice, maxPrice) = _resolveBoundedPrices(protectionActive, spot, uint256(updatedMin), uint256(updatedMax));\\n _setCachedPrices(asset, minPrice, maxPrice);\\n }\\n\\n /**\\n * @dev Expands the price window toward extremes if the spot price is a new min or max\\n * @param state The market protection state\\n * @param spot The current spot price\\n * @param asset The underlying asset address (for event emission)\\n */\\n function _expandPriceWindow(\\n MarketProtectionState storage state,\\n uint256 spot,\\n address asset\\n ) internal returns (uint128, uint128, bool) {\\n uint128 spotU128 = _safeToUint128(spot);\\n uint128 currentMin = state.minPrice;\\n uint128 currentMax = state.maxPrice;\\n bool windowExpanded;\\n if (spotU128 < currentMin) {\\n _setMinPrice(state, asset, spotU128);\\n currentMin = spotU128;\\n windowExpanded = true;\\n }\\n if (spotU128 > currentMax) {\\n _setMaxPrice(state, asset, spotU128);\\n currentMax = spotU128;\\n windowExpanded = true;\\n }\\n return (currentMin, currentMax, windowExpanded);\\n }\\n\\n /**\\n * @dev Checks if the spot price has deviated beyond the threshold and triggers protection.\\n * `lastProtectionTriggeredAt` is reset only on the first trigger or when the price has made a\\n * genuine new extreme this update (windowExpanded == true). Recovery within the existing window\\n * keeps the cooldown ticking so `exitProtectionMode` remains reachable.\\n * @param state The market protection state\\n * @param spot The current spot price\\n * @param asset The underlying asset address (for event emission)\\n * @param windowExpanded True if `_expandPriceWindow` recorded a new low or new high this call\\n */\\n function _checkAndTriggerProtection(\\n MarketProtectionState storage state,\\n uint256 spot,\\n address asset,\\n bool windowExpanded\\n ) internal returns (bool triggered) {\\n if (_exceedsDeviationThreshold(spot, state.minPrice, state.maxPrice, state.triggerThreshold)) {\\n bool enteringProtection = !state.currentlyUsingProtectedPrice;\\n if (enteringProtection || windowExpanded) {\\n state.lastProtectionTriggeredAt = uint64(block.timestamp);\\n }\\n if (enteringProtection) {\\n state.currentlyUsingProtectedPrice = true;\\n }\\n emit ProtectionTriggered(asset, spot, state.minPrice, state.maxPrice);\\n return true;\\n }\\n if (state.currentlyUsingProtectedPrice) return true;\\n }\\n\\n /**\\n * @notice Resolves the final bounded collateral and debt prices given a spot, window bounds, and protection flag.\\n * @dev When protection is active: collateral = min(spot, windowMin), debt = max(spot, windowMax).\\n * When protection is inactive: both return spot.\\n * @param protectionActive Whether the market protection window is currently active\\n * @param spot The current spot price\\n * @param windowMin The lower bound of the price window\\n * @param windowMax The upper bound of the price window\\n * @return minPrice The resolved lower-bound (collateral) price\\n * @return maxPrice The resolved upper-bound (debt) price\\n */\\n function _resolveBoundedPrices(\\n bool protectionActive,\\n uint256 spot,\\n uint256 windowMin,\\n uint256 windowMax\\n ) internal pure returns (uint256, uint256) {\\n if (!protectionActive) return (spot, spot);\\n return (spot < windowMin ? spot : windowMin, spot > windowMax ? spot : windowMax);\\n }\\n\\n /**\\n * @notice Shared view logic for all bounded price view functions.\\n * Checks transient cache first for an early return; on miss, fetches from oracle\\n * and computes both prices without state mutations.\\n * @param vToken vToken address\\n * @return minPrice The bounded lower (collateral) price\\n * @return maxPrice The bounded upper (debt) price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function _computeBoundedPrices(address vToken) internal view returns (uint256 minPrice, uint256 maxPrice) {\\n address asset = _getUnderlyingAsset(vToken);\\n\\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\\n (minPrice, maxPrice) = _getCachedPrices(asset);\\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\\n\\n // Cache miss \\u2014 fetch from oracle and compute without state mutations\\n uint256 spot = _fetchSpotPrice(asset);\\n MarketProtectionState storage state = assetProtectionConfig[asset];\\n if (!state.isBoundedPricingEnabled) return (spot, spot);\\n\\n // Mirror _expandPriceWindow logic: compute what the window would be after expansion\\n uint128 spotU128 = _safeToUint128(spot);\\n uint128 windowMin128 = spot < uint256(state.minPrice) ? spotU128 : state.minPrice;\\n uint128 windowMax128 = spot > uint256(state.maxPrice) ? spotU128 : state.maxPrice;\\n\\n bool shouldProtect = state.currentlyUsingProtectedPrice ||\\n _exceedsDeviationThreshold(spot, windowMin128, windowMax128, state.triggerThreshold);\\n\\n (minPrice, maxPrice) = _resolveBoundedPrices(shouldProtect, spot, uint256(windowMin128), uint256(windowMax128));\\n }\\n\\n /**\\n * @dev Computes the relative spread between the price window bounds as a ratio scaled by EXP_SCALE.\\n * Formula: \\\\((maxPrice - minPrice) / minPrice\\\\), scaled by `EXP_SCALE`.\\n * Used to measure how much the window has converged -- compared against `resetThreshold`\\n * to determine whether the price window is tight enough to exit protection mode.\\n * @param minPrice The minimum price in the window\\n * @param maxPrice The maximum price in the window\\n * @return The scaled bound ratio \\\\(((max - min) * EXP_SCALE) / min\\\\)\\n */\\n function _computePriceBoundRatio(uint128 minPrice, uint128 maxPrice) internal pure returns (uint256) {\\n uint256 range = uint256(maxPrice) - uint256(minPrice);\\n return (range * EXP_SCALE) / uint256(minPrice);\\n }\\n\\n /**\\n * @notice Checks whether the spot price has moved beyond the threshold relative to the\\n * opposite window bound \\u2014 i.e. `spot > minPrice * (1 + threshold)` or\\n * `spot < maxPrice * (1 - threshold)`.\\n * @dev Pump detection: spot > minPrice * (1 + threshold)\\n * Crash detection: spot < maxPrice * (1 - threshold)\\n * @param spot The current spot price\\n * @param minPrice The minimum price in the window\\n * @param maxPrice The maximum price in the window\\n * @param threshold The deviation threshold (mantissa)\\n * @return True if deviation is triggered\\n */\\n function _exceedsDeviationThreshold(\\n uint256 spot,\\n uint128 minPrice,\\n uint128 maxPrice,\\n uint256 threshold\\n ) internal pure returns (bool) {\\n uint256 upperBound = (uint256(minPrice) * (EXP_SCALE + threshold)) / EXP_SCALE;\\n uint256 lowerBound = (uint256(maxPrice) * (EXP_SCALE - threshold)) / EXP_SCALE;\\n return (spot > upperBound || spot < lowerBound);\\n }\\n\\n /**\\n * @dev Returns true if the relative drift between onChain and proposed exceeds KEEPER_DEADBAND\\n * @param currentPrice The current on-chain price\\n * @param proposedPrice The keeper's proposed price\\n * @return True if drift exceeds deadband\\n */\\n function _exceedsCorrectionDeadband(uint128 currentPrice, uint128 proposedPrice) internal pure returns (bool) {\\n if (currentPrice == 0 || proposedPrice == 0) return false;\\n uint256 diff = currentPrice > proposedPrice\\n ? uint256(currentPrice - proposedPrice)\\n : uint256(proposedPrice - currentPrice);\\n return (diff * EXP_SCALE) / uint256(currentPrice) > KEEPER_DEADBAND;\\n }\\n\\n /**\\n * @dev Sets the minimum price in the window and emits MinPriceUpdated\\n * @param state The market protection state\\n * @param asset The underlying asset address (for event emission)\\n * @param newMin The new minimum price\\n */\\n function _setMinPrice(MarketProtectionState storage state, address asset, uint128 newMin) internal {\\n emit MinPriceUpdated(asset, state.minPrice, newMin);\\n state.minPrice = newMin;\\n }\\n\\n /**\\n * @dev Sets the maximum price in the window and emits MaxPriceUpdated\\n * @param state The market protection state\\n * @param asset The underlying asset address (for event emission)\\n * @param newMax The new maximum price\\n */\\n function _setMaxPrice(MarketProtectionState storage state, address asset, uint128 newMax) internal {\\n emit MaxPriceUpdated(asset, state.maxPrice, newMax);\\n state.maxPrice = newMax;\\n }\\n\\n /**\\n * @dev Writes both lower and upper bounded prices to transient storage. No-ops when the\\n * asset's `cachingEnabled` flag is `false`, so callers that disable caching always\\n * fall through to live recomputation on subsequent reads.\\n * @param asset The underlying asset address\\n * @param minPrice The resolved lower (collateral) price to cache\\n * @param maxPrice The resolved upper (debt) price to cache\\n */\\n function _setCachedPrices(address asset, uint256 minPrice, uint256 maxPrice) internal {\\n if (!assetProtectionConfig[asset].cachingEnabled) return;\\n Transient.cachePrice(COLLATERAL_PRICE_CACHE_SLOT, asset, minPrice);\\n Transient.cachePrice(DEBT_PRICE_CACHE_SLOT, asset, maxPrice);\\n }\\n\\n /**\\n * @dev Reads a cached final price from transient storage. Returns `(0, 0)` when the\\n * asset's `cachingEnabled` flag is `false`, which callers already treat as a cache\\n * miss and handle via live recomputation.\\n * @param asset The underlying asset address\\n * @return minPrice The cached minimum price, or 0 on cache miss\\n * @return maxPrice The cached maximum price, or 0 on cache miss\\n */\\n function _getCachedPrices(address asset) internal view returns (uint256 minPrice, uint256 maxPrice) {\\n if (!assetProtectionConfig[asset].cachingEnabled) return (0, 0);\\n minPrice = Transient.readCachedPrice(COLLATERAL_PRICE_CACHE_SLOT, asset);\\n maxPrice = Transient.readCachedPrice(DEBT_PRICE_CACHE_SLOT, asset);\\n }\\n\\n /**\\n * @dev This function returns the underlying asset of a vToken\\n * @param vToken vToken address\\n * @return asset underlying asset address\\n */\\n function _getUnderlyingAsset(address vToken) private view returns (address asset) {\\n ensureNonzeroAddress(vToken);\\n if (vToken == nativeMarket) {\\n asset = NATIVE_TOKEN_ADDR;\\n } else if (vToken == vai) {\\n asset = vai;\\n } else {\\n asset = VBep20Interface(vToken).underlying();\\n }\\n }\\n\\n /**\\n * @dev Reverts if the market has not been initialized via setTokenConfig\\n * @param asset The underlying asset address\\n * @return state The market protection state storage pointer\\n */\\n function _ensureInitialized(address asset) internal view returns (MarketProtectionState storage state) {\\n state = assetProtectionConfig[asset];\\n if (state.asset == address(0)) revert MarketNotInitialized(asset);\\n }\\n\\n /**\\n * @notice Fetches the current spot price for an asset from the ResilientOracle\\n * @param asset The underlying asset address\\n * @return The current spot price\\n */\\n function _fetchSpotPrice(address asset) internal view returns (uint256) {\\n return RESILIENT_ORACLE.getPrice(asset);\\n }\\n\\n /**\\n * @dev Safely casts a uint256 to uint128, reverting on overflow\\n * @param value The value to cast\\n * @return The value as uint128\\n */\\n function _safeToUint128(uint256 value) internal pure returns (uint128) {\\n if (value > type(uint128).max) revert PriceExceedsUint128(value);\\n return uint128(value);\\n }\\n}\\n\",\"keccak256\":\"0x753d4b0caf8d71739c8615b1cdcc751f45576182ab82cd5859102c16e4402e0d\",\"license\":\"BSD-3-Clause\"},\"contracts/interfaces/IDeviationBoundedOracle.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity 0.8.25;\\n\\ninterface IDeviationBoundedOracle {\\n // --- Enums ---\\n\\n /// @notice Identifies whether a price bound is a minimum or maximum\\n enum PriceBoundType {\\n MIN,\\n MAX\\n }\\n\\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\\n enum KeeperAction {\\n SetMinPrice,\\n SetMaxPrice,\\n ExitProtectionMode\\n }\\n\\n // --- Structs ---\\n\\n /// @notice Per-asset protection state tracking the min/max price window\\n struct MarketProtectionState {\\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\\n uint128 minPrice;\\n /// @notice Highest price observed in the current window\\n uint128 maxPrice;\\n /// @notice Whether protected price is currently being used\\n bool currentlyUsingProtectedPrice;\\n /// @notice Whether this market is whitelisted for bounded pricing\\n bool isBoundedPricingEnabled;\\n /// @notice Timestamp of the last protection trigger \\u2014 reset on every trigger\\n uint64 lastProtectionTriggeredAt;\\n /// @notice Minimum time protection stays active after last trigger\\n uint64 cooldownPeriod;\\n /// @notice The underlying asset address, used to verify initialization\\n address asset;\\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\\n uint128 triggerThreshold;\\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\\n uint128 resetThreshold;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool cachingEnabled;\\n }\\n\\n /// @notice One item in an syncPriceBoundsAndProtections payload\\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\\n struct KeeperActionItem {\\n address asset;\\n KeeperAction action;\\n uint256 value;\\n }\\n\\n /// @notice One item in a setTokenConfigs payload\\n struct TokenConfigInput {\\n /// @notice The underlying asset address\\n address asset;\\n /// @notice Minimum time protection stays active after the last trigger (seconds)\\n uint64 cooldownPeriod;\\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\\n uint256 triggerThreshold;\\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\\n uint256 resetThreshold;\\n /// @notice Whether to enable bounded pricing immediately upon initialization\\n bool enableBoundedPricing;\\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\\n bool enableCaching;\\n }\\n\\n // --- Events ---\\n\\n /// @notice Emitted when protection is initialized for an asset\\n event ProtectionInitialized(\\n address indexed asset,\\n uint128 minPrice,\\n uint128 maxPrice,\\n uint64 cooldownPeriod,\\n uint256 triggerThreshold\\n );\\n\\n /// @notice Emitted when protection mode is triggered for an asset\\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\\n\\n /// @notice Emitted when protection mode is disabled for an asset\\n event ProtectionModeExited(address indexed asset);\\n\\n /// @notice Emitted when the keeper updates the minimum price for an asset\\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\\n\\n /// @notice Emitted when the keeper updates the maximum price for an asset\\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\\n\\n /// @notice Emitted when the entry threshold is updated for an asset\\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\\n\\n /// @notice Emitted when the exit threshold is updated for an asset\\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\\n\\n /// @notice Emitted when the cooldown period is updated for an asset\\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\\n\\n /// @notice Emitted when an asset's whitelist status changes\\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\\n\\n /// @notice Emitted when the per-asset transient caching flag is toggled\\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\\n\\n // --- Errors ---\\n\\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\\n error MarketNotInitialized(address asset);\\n\\n /// @notice Thrown when trying to initialize an already initialized market\\n error MarketAlreadyInitialized(address asset);\\n\\n /// @notice Thrown when trying to disable protection that is not active\\n error ProtectedPriceInactive(address asset);\\n\\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\\n\\n /// @notice Thrown when trying to disable protection before price range has converged\\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\\n\\n /// @notice Thrown when keeper tries to set minPrice above current spot\\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\\n\\n /// @notice Thrown when keeper tries to set maxPrice below current spot\\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\\n\\n /// @notice Thrown when threshold is set below the minimum allowed value\\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\\n\\n /// @notice Thrown when threshold is set above the maximum allowed value\\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\\n\\n /// @notice Thrown when a price exceeds uint128 max\\n error PriceExceedsUint128(uint256 price);\\n\\n /// @notice Thrown when a zero price is provided where a non-zero price is required\\n error ZeroPriceNotAllowed();\\n\\n /// @notice Thrown when trying to initialize protection for VAI\\n error VAINotAllowed();\\n\\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\\n error ProtectedPriceActive(address asset);\\n\\n /// @notice Thrown when the lengths of the arrays are not equal\\n error InvalidArrayLength();\\n\\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\\n error InvalidResetThreshold(uint256 resetThreshold);\\n\\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\\n error InvalidKeeperAction(uint8 action);\\n\\n // --- Non-view price functions (update window + trigger protection) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- State update (call before view price reads to populate transient cache) ---\\n\\n /**\\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\\n * reads in the same transaction are served from transient storage. The transient\\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\\n * @param vToken vToken address\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event MinPriceUpdated if a new window minimum is recorded\\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\\n */\\n function updateProtectionState(address vToken) external;\\n\\n // --- View price functions (read stored/cached state only) ---\\n\\n /**\\n * @notice Gets the bounded collateral price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded collateral price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets the bounded debt price for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return price The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\\n\\n /**\\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\\n * falls back to ResilientOracle on cache miss or when caching is disabled.\\n * @param vToken vToken address\\n * @return collateralPrice The bounded collateral price\\n * @return debtPrice The bounded debt price\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\\n */\\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\\n\\n // --- Keeper functions ---\\n\\n /**\\n * @notice Updates the minimum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMin is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\\n * @custom:event MinPriceUpdated\\n */\\n function updateMinPrice(address asset, uint128 newMin) external;\\n\\n /**\\n * @notice Updates the maximum price in the rolling window for a given asset\\n * @param asset The underlying asset address\\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\\n * @custom:access Only authorized keeper addresses\\n * @custom:error ZeroPriceNotAllowed if newMax is zero\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\\n * @custom:event MaxPriceUpdated\\n */\\n function updateMaxPrice(address asset, uint128 newMax) external;\\n\\n /**\\n * @notice Exits protection mode for a given asset once conditions are met\\n * @param asset The underlying asset address\\n * @custom:access Only authorized monitor/keeper addresses\\n * @custom:error ProtectedPriceInactive if protection is not currently active\\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\\n * @custom:event ProtectionModeExited\\n */\\n function exitProtectionMode(address asset) external;\\n\\n /**\\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\\n * Empty `actions` is a no-op success.\\n * @param actions The list of keeper actions to apply\\n * @custom:access Only authorized keeper addresses\\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\\n */\\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\\n\\n // --- Admin functions (governance-gated) ---\\n\\n /**\\n * @notice Initializes protection parameters for a new asset\\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\\n * confirming the oracle is live for this asset before it is listed.\\n * @param tokenConfig_ Token config input for the asset\\n * @custom:access Only Governance\\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\\n * @custom:error VAINotAllowed if asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\\n * @custom:event ProtectionInitialized\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\\n\\n /**\\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\\n * @param tokenConfigs_ Array of token config inputs, one per asset\\n * @custom:access Only Governance\\n * @custom:error InvalidArrayLength if the input array is empty\\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\\n * @custom:error VAINotAllowed if any asset is the VAI token\\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\\n * @custom:event ProtectionInitialized for each asset\\n * @custom:event BoundedPricingWhitelistUpdated for each asset\\n */\\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\\n\\n /**\\n * @notice Sets the cooldown period for an asset\\n * @param asset The underlying asset address\\n * @param newCooldown The new cooldown period in seconds; must be non-zero\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CooldownPeriodSet\\n */\\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\\n\\n /**\\n * @notice Sets the trigger and reset thresholds for an asset\\n * @param asset The underlying asset address\\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\\n * @custom:event TriggerThresholdSet if the trigger threshold changed\\n * @custom:event ResetThresholdSet if the reset threshold changed\\n */\\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\\n\\n /**\\n * @notice Sets whether bounded pricing is enabled for an asset\\n * @param asset The underlying asset address\\n * @param enabled Whether bounded pricing should be enabled for the asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\\n * @custom:event BoundedPricingWhitelistUpdated\\n */\\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\\n\\n /**\\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\\n * live spot instead of reading or writing the transient slots. The initial value is\\n * set via the `enableCaching` argument of `setTokenConfig`.\\n * @param asset The underlying asset address\\n * @param enabled Whether transient caching is enabled for this asset\\n * @custom:access Only Governance\\n * @custom:error MarketNotInitialized if the asset has not been initialized\\n * @custom:event CachingEnabledUpdated\\n */\\n function setCachingEnabled(address asset, bool enabled) external;\\n\\n // --- View helpers ---\\n\\n /**\\n * @notice Returns the full protection state for an asset\\n * @param asset The underlying asset address\\n * @return minPrice Lowest price observed in the current window\\n * @return maxPrice Highest price observed in the current window\\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\\n * @return cooldownPeriod Minimum time protection stays active after last trigger\\n * @return assetAddr The underlying asset address stored in the struct\\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\\n */\\n function assetProtectionConfig(\\n address asset\\n )\\n external\\n view\\n returns (\\n uint128 minPrice,\\n uint128 maxPrice,\\n bool currentlyUsingProtectedPrice,\\n bool isBoundedPricingEnabled,\\n uint64 lastProtectionTriggeredAt,\\n uint64 cooldownPeriod,\\n address assetAddr,\\n uint128 triggerThreshold,\\n uint128 resetThreshold,\\n bool cachingEnabled\\n );\\n\\n /**\\n * @notice Checks if an asset is whitelisted for bounded pricing\\n * @param asset The underlying asset address\\n * @return True if the asset is whitelisted\\n */\\n function isBoundedPricingEnabled(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if the asset is currently using the protected (bounded) price\\n * @param asset The underlying asset address\\n * @return True if the asset is currently using the protected price instead of spot\\n */\\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\\n\\n /**\\n * @notice Checks if protection can be exited for a given asset\\n * @param asset The underlying asset address\\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\\n */\\n function canExitProtection(address asset) external view returns (bool);\\n\\n /**\\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\\n * @param index Array index\\n * @return The asset address at the given index\\n */\\n function allAssets(uint256 index) external view returns (address);\\n\\n /**\\n * @notice Returns all currently whitelisted asset addresses\\n * @return result Array of whitelisted asset addresses\\n */\\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\\n\\n /**\\n * @notice Returns all asset addresses that have ever been initialized\\n * @return Array of all initialized asset addresses\\n */\\n function getInitializedAssets() external view returns (address[] memory);\\n\\n /**\\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\\n * @param assets Array of asset addresses to check\\n * @param proposedMins Keeper's proposed window minimum prices\\n * @param proposedMaxs Keeper's proposed window maximum prices\\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\\n * @custom:error InvalidArrayLength if the input array lengths do not match\\n */\\n function checkAndGetWindowDrift(\\n address[] calldata assets,\\n uint128[] calldata proposedMins,\\n uint128[] calldata proposedMaxs\\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\\n}\\n\",\"keccak256\":\"0xe223d88c17c0d752fdb33fa223b63ce124a798512f3d69f3800e9508e7dff931\",\"license\":\"BSD-3-Clause\"},\"contracts/interfaces/OracleInterface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\ninterface OracleInterface {\\n function getPrice(address asset) external view returns (uint256);\\n}\\n\\ninterface ResilientOracleInterface is OracleInterface {\\n function updatePrice(address vToken) external;\\n\\n function updateAssetPrice(address asset) external;\\n\\n function getUnderlyingPrice(address vToken) external view returns (uint256);\\n}\\n\\ninterface BoundValidatorInterface {\\n function validatePriceWithAnchorPrice(\\n address asset,\\n uint256 reporterPrice,\\n uint256 anchorPrice\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xd3bbb7c9eef19e8f467342df6034ef95399a00964646fb8c82b438968ae3a8c0\",\"license\":\"BSD-3-Clause\"},\"contracts/interfaces/VBep20Interface.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\ninterface VBep20Interface is IERC20Metadata {\\n /**\\n * @notice Underlying asset for this VToken\\n */\\n function underlying() external view returns (address);\\n}\\n\",\"keccak256\":\"0x6e71c3df86501df5c0e4bace1333c0c91f9f9cced252a54fb99eeda219b789d5\",\"license\":\"BSD-3-Clause\"},\"contracts/lib/Transient.sol\":{\"content\":\"// SPDX-License-Identifier: BSD-3-Clause\\npragma solidity ^0.8.25;\\n\\nlibrary Transient {\\n /**\\n * @notice Cache the asset price into transient storage\\n * @param key address of the asset\\n * @param value asset price\\n */\\n function cachePrice(bytes32 cacheSlot, address key, uint256 value) internal {\\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\\n assembly (\\\"memory-safe\\\") {\\n tstore(slot, value)\\n }\\n }\\n\\n /**\\n * @notice Read cached price from transient storage\\n * @param key address of the asset\\n * @return value cached asset price\\n */\\n function readCachedPrice(bytes32 cacheSlot, address key) internal view returns (uint256 value) {\\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\\n assembly (\\\"memory-safe\\\") {\\n value := tload(slot)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x60d7133a48a757ee777cb9230e890ef489ffc33dcea9dadfcf5a8b72f9dd43aa\",\"license\":\"BSD-3-Clause\"}},\"version\":1}", + "bytecode": "0x60e060405234801561000f575f80fd5b5060405161345438038061345483398101604081905261002e91610163565b61003783610069565b61004082610069565b6001600160a01b0380841660805282811660a052811660c052610061610093565b5050506101ad565b6001600160a01b038116610090576040516342bcdf7f60e11b815260040160405180910390fd5b50565b5f54610100900460ff16156100fe5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b5f5460ff9081161461014d575f805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6001600160a01b0381168114610090575f80fd5b5f805f60608486031215610175575f80fd5b83516101808161014f565b60208501519093506101918161014f565b60408501519092506101a28161014f565b809150509250925092565b60805160a05160c05161325a6101fa5f395f818161058501528181611c610152818161258001526125ba01525f818161048d015261252c01525f81816104fe01526119d2015261325a5ff3fe608060405234801561000f575f80fd5b5060043610610255575f3560e01c806388142b6b11610140578063b540894f116100bf578063cf1412c011610084578063cf1412c0146106ba578063dcc88962146106cd578063e2201bb8146106e0578063e30c3978146106f3578063e40c2fed14610704578063f2fde38b14610717575f80fd5b8063b540894f1461055f578063b62e4c9214610580578063bd11c4c0146103fe578063c3821757146105a7578063c4d66de8146106a7575f80fd5b8063a31ebe5811610105578063a31ebe58146104e6578063a4edcd4c146104f9578063a9534f8a14610520578063a9c3cab11461053b578063b4a0bdf31461054e575f80fd5b806388142b6b146104755780638a2f7f6d146104885780638cf38bb1146104af5780638da5cb5b146104c2578063969f58d3146104d3575f80fd5b80634912c452116101d757806376489e381161019c57806376489e38146103b357806379ba5097146103f65780637f5e1328146103fe5780637fa6ea501461040c578063870dc597146104345780638769b23114610447575f80fd5b80634912c4521461034757806352a6bce31461035a578063578e5c221461036d5780636121316814610398578063715018a6146103ab575f80fd5b80631be74faf1161021d5780631be74faf146102f157806323013b83146103065780632f7b5dd7146103195780633b4ecdb21461032c578063412b6ec714610334575f80fd5b806308af5431146102595780630a5fa04d1461027b5780630e32cb86146102a25780631169d5a6146102b757806315a53122146102de575b5f80fd5b6102686706f05b59d3b2000081565b6040519081526020015b60405180910390f35b6102687f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb897060081565b6102b56102b0366004612b01565b61072a565b005b6102687f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf700081565b6102b56102ec366004612b32565b61073e565b6102f961078b565b6040516102729190612b65565b6102b5610314366004612bbe565b61093e565b6102b5610327366004612bf5565b610a6a565b6102f9610b33565b6102b5610342366004612bbe565b610b93565b6102b5610355366004612c63565b610c43565b610268610368366004612b01565b610e3c565b61038061037b366004612c95565b610e4d565b6040516001600160a01b039091168152602001610272565b6102b56103a6366004612cac565b610e75565b6102b5610ea6565b6103e66103c1366004612b01565b6001600160a01b03165f90815260c96020526040902060010154610100900460ff1690565b6040519015158152602001610272565b6102b5610eb9565b61026866b1a2bc2ec5000081565b61041f61041a366004612b01565b610f30565b60408051928352602083019190915201610272565b6102b5610442366004612cc2565b610f44565b6103e6610455366004612b01565b6001600160a01b03165f90815260c9602052604090206001015460ff1690565b61041f610483366004612b01565b6110af565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b6102b56104bd366004612b01565b6110ba565b6033546001600160a01b0316610380565b6102b56104e1366004612b32565b611101565b6102b56104f4366004612d34565b61114b565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b61038073bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb81565b6102b5610549366004612b01565b61121e565b6097546001600160a01b0316610380565b61057261056d366004612da5565b61122c565b604051610272929190612e73565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b6106326105b5366004612b01565b60c96020525f9081526040902080546001820154600283015460038401546004909401546001600160801b0380851695600160801b9586900482169560ff8087169661010081048216966001600160401b03620100008304811697600160501b90930416956001600160a01b03909116948082169490041691168a565b604080516001600160801b039b8c168152998b1660208b01529715159789019790975294151560608801526001600160401b0393841660808801529290911660a08601526001600160a01b031660c0850152841660e08401529290921661010082015290151561012082015261014001610272565b6102b56106b5366004612b01565b6113f2565b6102686106c8366004612b01565b6114ff565b6103e66106db366004612b01565b611510565b6102686106ee366004612b01565b61159c565b6065546001600160a01b0316610380565b610268610712366004612b01565b6115a6565b6102b5610725366004612b01565b6115b0565b610732611621565b61073b8161167b565b50565b61077c6040518060400160405280601f81526020017f7570646174654d696e507269636528616464726573732c75696e743132382900815250611739565b61078782825f6117d0565b5050565b60ca546060905f816001600160401b038111156107aa576107aa612ea0565b6040519080825280602002602001820160405280156107d3578160200160208202803683370190505b5090505f805b838110156108995760c95f60ca83815481106107f7576107f7612eb4565b5f9182526020808320909101546001600160a01b0316835282019290925260400190206001015460ff61010090910416156108915760ca818154811061083f5761083f612eb4565b5f918252602090912001546001600160a01b0316838361085e81612edc565b94508151811061087057610870612eb4565b60200260200101906001600160a01b031690816001600160a01b0316815250505b6001016107d9565b505f816001600160401b038111156108b3576108b3612ea0565b6040519080825280602002602001820160405280156108dc578160200160208202803683370190505b5090505f5b82811015610935578381815181106108fb576108fb612eb4565b602002602001015182828151811061091557610915612eb4565b6001600160a01b03909216602092830291909101909101526001016108e1565b50949350505050565b61095f6040518060600160405280602b8152602001613104602b9139611739565b6109688261193d565b5f61097283611964565b9050811580156109865750600181015460ff165b156109b4576040516310ce5af160e11b81526001600160a01b03841660048201526024015b60405180910390fd5b8115158160010160019054906101000a900460ff161515036109d557505050565b8115610a07575f6109ed6109e8856119b1565b611a43565b90506109fa828583611a73565b610a05828583611ae3565b505b6001810180548315156101000261ff00199091161790556040516001600160a01b038416907fde52a1c70ed1ca343b25cca640873d949641bd6ec7a2c2d0e67374ce54ff89cd90610a5d90851515815260200190565b60405180910390a2505050565b610a8b6040518060600160405280603d815260200161312f603d9139611739565b805f819003610aad57604051634ec4810560e11b815260040160405180910390fd5b5f5b81811015610b2d5736848483818110610aca57610aca612eb4565b60c002919091019150610b249050610ae56020830183612b01565b610af56040840160208501612ef4565b60408401356060850135610b0f60a0870160808801612f0d565b610b1f60c0880160a08901612f0d565b611b57565b50600101610aaf565b50505050565b606060ca805480602002602001604051908101604052809291908181526020018280548015610b8957602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311610b6b575b5050505050905090565b610bd16040518060400160405280601f81526020017f73657443616368696e67456e61626c656428616464726573732c626f6f6c2900815250611739565b5f610bdb83611964565b60048101546040805160ff9092161515825284151560208301529192506001600160a01b038516917f80b87a057217c1d7743a43adf6fcf8a06553fde1205440ebbfb539f6f0e04424910160405180910390a2600401805460ff191691151591909117905550565b610c6460405180606001604052806026815260200161316c60269139611739565b610c6d8361193d565b610c768261200e565b610c7f8161200e565b66b1a2bc2ec50000821015610cb757604051630f4d736160e01b81526004810183905266b1a2bc2ec5000060248201526044016109ab565b6706f05b59d3b20000821115610cf1576040516350ddbb7560e11b8152600481018390526706f05b59d3b2000060248201526044016109ab565b818110610d1457604051632021db5560e21b8152600481018290526024016109ab565b5f610d1e84611964565b60038101549091506001600160801b03168314610da6576003810154604080516001600160801b039092168252602082018590526001600160a01b038616917f605baae98875f2133a40a8d489531e195b473e8cdb0c860a2d2b51d430eff588910160405180910390a26003810180546001600160801b0319166001600160801b0385161790555b6003810154600160801b90046001600160801b03168214610b2d57600381015460408051600160801b9092046001600160801b03168252602082018490526001600160a01b038616917f75c087963c80520a0d86e6333991c95d79462341f6960f2904396c23def23dd4910160405180910390a26003810180546001600160801b03808516600160801b02911617905550505050565b5f610e468261202e565b9392505050565b60ca8181548110610e5c575f80fd5b5f918252602090912001546001600160a01b0316905081565b610e966040518060600160405280603a8152602001613192603a9139611739565b61073b610ae56020830183612b01565b610eae611621565b610eb75f61210c565b565b60655433906001600160a01b03168114610f275760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016109ab565b61073b8161210c565b5f80610f3b8361202e565b91509150915091565b610f656040518060600160405280603881526020016131cc60389139611739565b805f5b81811015610b2d5736848483818110610f8357610f83612eb4565b6060029190910191505f9050610f9f6040830160208401612f3c565b6002811115610fb057610fb0612f28565b03610fdd57610fd8610fc56020830183612b01565b610fd28360400135611a43565b5f6117d0565b6110a6565b6001610fef6040830160208401612f3c565b600281111561100057611000612f28565b0361102957610fd86110156020830183612b01565b6110228360400135611a43565b60016117d0565b600261103b6040830160208401612f3c565b600281111561104c5761104c612f28565b0361106657610fd86110616020830183612b01565b612125565b6110766040820160208301612f3c565b600281111561108757611087612f28565b604051635374467d60e11b815260ff90911660048201526024016109ab565b50600101610f68565b5f80610f3b836122b9565b6110f86040518060400160405280601b81526020017f6578697450726f74656374696f6e4d6f64652861646472657373290000000000815250611739565b61073b81612125565b61113f6040518060400160405280601f81526020017f7570646174654d6178507269636528616464726573732c75696e743132382900815250611739565b610787828260016117d0565b61116c60405180606001604052806021815260200161320460219139611739565b6111758261193d565b611187816001600160401b031661200e565b5f61119183611964565b6001810154604080516001600160401b03600160501b9093048316815291851660208301529192506001600160a01b038516917f890c8f3ce148a0c50a68d44c085aa3228b8e72273a03dba4ecfd402f94033c1e910160405180910390a260010180546001600160401b03909216600160501b0267ffffffffffffffff60501b1990921691909117905550565b6112278161202e565b505050565b60608086858114158061123f5750808414155b1561125d57604051634ec4810560e11b815260040160405180910390fd5b806001600160401b0381111561127557611275612ea0565b60405190808252806020026020018201604052801561129e578160200160208202803683370190505b509250806001600160401b038111156112b9576112b9612ea0565b6040519080825280602002602001820160405280156112e2578160200160208202803683370190505b5091505f5b818110156113e5575f60c95f8c8c8581811061130557611305612eb4565b905060200201602081019061131a9190612b01565b6001600160a01b0316815260208101919091526040015f208054909150611370906001600160801b03168a8a8581811061135657611356612eb4565b905060200201602081019061136b9190612f5a565b6123f8565b85838151811061138257611382612eb4565b9115156020928302919091019091015280546113ba90600160801b90046001600160801b031688888581811061135657611356612eb4565b8483815181106113cc576113cc612eb4565b91151560209283029190910190910152506001016112e7565b5050965096945050505050565b5f54610100900460ff161580801561141057505f54600160ff909116105b806114295750303b15801561142957505f5460ff166001145b61148c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016109ab565b5f805460ff1916600117905580156114ad575f805461ff0019166101001790555b6114b6826124a1565b8015610787575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15050565b5f6115098261202e565b5092915050565b6001600160a01b0381165f90815260c960205260408120600181015460ff1680156115615750600181015461155d906001600160401b03600160501b820481169162010000900416612f73565b4210155b8015610e465750600381015481546001600160801b03600160801b928390048116926115949280831692919004166124d8565b109392505050565b5f610e46826122b9565b5f611509826122b9565b6115b8611621565b606580546001600160a01b0383166001600160a01b031990911681179091556115e96033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6033546001600160a01b03163314610eb75760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109ab565b6001600160a01b0381166116df5760405162461bcd60e51b815260206004820152602560248201527f696e76616c696420616365737320636f6e74726f6c206d616e61676572206164604482015264647265737360d81b60648201526084016109ab565b609780546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa091016114f3565b6097546040516318c5e8ab60e01b81525f916001600160a01b0316906318c5e8ab9061176b9033908690600401612fb4565b602060405180830381865afa158015611786573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117aa9190612fd7565b90508061078757333083604051634a3fa29360e01b81526004016109ab93929190612ff2565b6117d98361193d565b816001600160801b03165f036118025760405163a2b326dd60e01b815260040160405180910390fd5b5f61180c84611964565b90505f611818856119b1565b90505f83600181111561182d5761182d612f28565b036118af5781546001600160801b03600160801b9091048116908516118061185d575080846001600160801b0316115b1561189f5760405160016203590160e31b031981526001600160a01b03861660048201526001600160801b0385166024820152604481018290526064016109ab565b6118aa828686611a73565b611936565b60018360018111156118c3576118c3612f28565b036119365781546001600160801b0390811690851610806118ec575080846001600160801b0316105b1561192b57604051636df018fb60e11b81526001600160a01b03861660048201526001600160801b0385166024820152604481018290526064016109ab565b611936828686611ae3565b5050505050565b6001600160a01b03811661073b576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160a01b038082165f90815260c96020526040902060028101549091166119ac576040516349c9b68960e11b81526001600160a01b03831660048201526024016109ab565b919050565b6040516341976e0960e01b81526001600160a01b0382811660048301525f917f0000000000000000000000000000000000000000000000000000000000000000909116906341976e0990602401602060405180830381865afa158015611a19573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a3d919061301d565b92915050565b5f6001600160801b03821115611a6f5760405163339718d960e11b8152600481018390526024016109ab565b5090565b8254604080516001600160801b03928316815291831660208301526001600160a01b038416917f532579a79f45c3e02b749f0817d46bab6b256e8d1dae660d2f432d8032f21f2d910160405180910390a282546001600160801b0319166001600160801b03919091161790915550565b825460408051600160801b9092046001600160801b039081168352831660208301526001600160a01b038416917f067a69521c594f269500a8d1fd306d6cade49e09512a3fdf637a16aa1694dd88910160405180910390a282546001600160801b03918216600160801b0291161790915550565b611b608661193d565b611b72856001600160401b031661200e565b611b7b8461200e565b611b848361200e565b6001600160a01b038681165f90815260c960205260409020600201541615611bca57604051630d8cdd9d60e01b81526001600160a01b03871660048201526024016109ab565b66b1a2bc2ec50000841015611c0257604051630f4d736160e01b81526004810185905266b1a2bc2ec5000060248201526044016109ab565b6706f05b59d3b20000841115611c3c576040516350ddbb7560e11b8152600481018590526706f05b59d3b2000060248201526044016109ab565b838310611c5f57604051632021db5560e21b8152600481018490526024016109ab565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316866001600160a01b031603611cb15760405163c992a64b60e01b815260040160405180910390fd5b5f611cbe6109e8886119b1565b9050604051806101400160405280826001600160801b03168152602001826001600160801b031681526020015f1515815260200184151581526020015f6001600160401b03168152602001876001600160401b03168152602001886001600160a01b03168152602001866001600160801b03168152602001856001600160801b0316815260200183151581525060c95f896001600160a01b03166001600160a01b031681526020019081526020015f205f820151815f015f6101000a8154816001600160801b0302191690836001600160801b031602179055506020820151815f0160106101000a8154816001600160801b0302191690836001600160801b031602179055506040820151816001015f6101000a81548160ff02191690831515021790555060608201518160010160016101000a81548160ff02191690831515021790555060808201518160010160026101000a8154816001600160401b0302191690836001600160401b0316021790555060a082015181600101600a6101000a8154816001600160401b0302191690836001600160401b0316021790555060c0820151816002015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060e0820151816003015f6101000a8154816001600160801b0302191690836001600160801b031602179055506101008201518160030160106101000a8154816001600160801b0302191690836001600160801b03160217905550610120820151816004015f6101000a81548160ff02191690831515021790555090505060ca87908060018154018082558091505060019003905f5260205f20015f9091909190916101000a8154816001600160a01b0302191690836001600160a01b03160217905550866001600160a01b03167feaeb53cd979b528911d7ed793f11a25e44e5076bef70f458f60049acb4c322b682838989604051611fb894939291906001600160801b0394851681529290931660208301526001600160401b03166040820152606081019190915260800190565b60405180910390a2866001600160a01b03167fde52a1c70ed1ca343b25cca640873d949641bd6ec7a2c2d0e67374ce54ff89cd84604051611ffd911515815260200190565b60405180910390a250505050505050565b805f0361073b5760405163273e150360e21b815260040160405180910390fd5b5f805f61203a84612520565b90506120458161263e565b9093509150821580159061205857508115155b156120635750915091565b5f61206d826119b1565b6001600160a01b0383165f90815260c960205260409020600181015491925090610100900460ff166120af576120a48383846126c9565b509485945092505050565b5f805f6120bd848688612746565b9250925092505f6120d0858789856127c7565b90506120f08187866001600160801b0316866001600160801b03166128ce565b9099509750612100878a8a6126c9565b50505050505050915091565b606580546001600160a01b031916905561073b8161290b565b61212e8161193d565b5f61213882611964565b600181015490915060ff1661216b57604051638e001e1f60e01b81526001600160a01b03831660048201526024016109ab565b6001810154612192906001600160401b03600160501b820481169162010000900416612f73565b4210156121e7576001810154604051630874f3a160e01b81526001600160a01b03841660048201526001600160401b0362010000830481166024830152600160501b90920490911660448201526064016109ab565b80545f90612208906001600160801b0380821691600160801b9004166124d8565b6003830154909150600160801b90046001600160801b0316811061226c5760038201546040516311c9371560e01b81526001600160a01b038516600482015260248101839052600160801b9091046001600160801b031660448201526064016109ab565b60018201805469ffffffffffffffff00ff191690556040516001600160a01b038416907f6977cf3ab0aaccc6ceeb77ddaf93b2e1f1b9e6c7e39c7fb08314efbbea365741905f90a2505050565b5f805f6122c584612520565b90506122d08161263e565b909350915082158015906122e357508115155b156122ee5750915091565b5f6122f8826119b1565b6001600160a01b0383165f90815260c960205260409020600181015491925090610100900460ff1661232f57509485945092505050565b5f61233983611a43565b82549091505f906001600160801b0316841061235f5782546001600160801b0316612361565b815b83549091505f90600160801b90046001600160801b03168511612395578354600160801b90046001600160801b0316612397565b825b60018501549091505f9060ff16806123c7575060038501546123c7908790859085906001600160801b031661295c565b90506123e78187856001600160801b0316856001600160801b03166128ce565b909b909a5098505050505050505050565b5f6001600160801b038316158061241657506001600160801b038216155b1561242257505f611a3d565b5f826001600160801b0316846001600160801b031611612454576124468484613034565b6001600160801b0316612468565b61245e8385613034565b6001600160801b03165b905066b1a2bc2ec500006001600160801b03851661248e670de0b6b3a764000084613054565b612498919061306b565b11949350505050565b5f54610100900460ff166124c75760405162461bcd60e51b81526004016109ab9061308a565b6124cf6129db565b61073b81612a09565b5f806124f06001600160801b038086169085166130d5565b90506001600160801b03841661250e670de0b6b3a764000083613054565b612518919061306b565b949350505050565b5f61252a8261193d565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361257e575073bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb919050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316036125de57507f0000000000000000000000000000000000000000000000000000000000000000919050565b816001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561261a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a3d91906130e8565b6001600160a01b0381165f90815260c96020526040812060040154819060ff1661266c57505f928392509050565b6126967f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf700084612a2f565b91506126c27f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb897060084612a2f565b9050915091565b6001600160a01b0383165f90815260c9602052604090206004015460ff166126f057505050565b61271b7f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf70008484612a77565b6112277f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb89706008483612a77565b5f805f8061275386611a43565b87549091506001600160801b0380821691600160801b90048116905f90841683111561278b576127848a8986611a73565b5082915060015b816001600160801b0316846001600160801b031611156127b7576127b08a8986611ae3565b5082905060015b9199909850909650945050505050565b835460038501545f916127f39186916001600160801b0380821692600160801b9092048116911661295c565b156128b657600185015460ff1615808061280a5750825b156128355760018601805469ffffffffffffffff0000191662010000426001600160401b0316021790555b801561284c576001868101805460ff191690911790555b8554604080518781526001600160801b038084166020830152600160801b909304909216908201526001600160a01b038516907f0e25941e7a04e3bd937c28c403c9431e5fe5cf87df19322b5352bf457f8b1c5f9060600160405180910390a26001915050612518565b600185015460ff161561251857506001949350505050565b5f80856128df575083905080612902565b8385106128ec57836128ee565b845b8386116128fb57836128fd565b855b915091505b94509492505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f80670de0b6b3a76400006129718482612f73565b612984906001600160801b038816613054565b61298e919061306b565b90505f670de0b6b3a76400006129a485826130d5565b6129b7906001600160801b038816613054565b6129c1919061306b565b9050818711806129d057508087105b979650505050505050565b5f54610100900460ff16612a015760405162461bcd60e51b81526004016109ab9061308a565b610eb7612abe565b5f54610100900460ff166107325760405162461bcd60e51b81526004016109ab9061308a565b5f808383604051602001612a569291909182526001600160a01b0316602082015260400190565b60408051601f1981840301815291905280516020909101205c949350505050565b5f8383604051602001612a9d9291909182526001600160a01b0316602082015260400190565b60405160208183030381529060405280519060200120905081815d50505050565b5f54610100900460ff16612ae45760405162461bcd60e51b81526004016109ab9061308a565b610eb73361210c565b6001600160a01b038116811461073b575f80fd5b5f60208284031215612b11575f80fd5b8135610e4681612aed565b80356001600160801b03811681146119ac575f80fd5b5f8060408385031215612b43575f80fd5b8235612b4e81612aed565b9150612b5c60208401612b1c565b90509250929050565b602080825282518282018190525f9190848201906040850190845b81811015612ba55783516001600160a01b031683529284019291840191600101612b80565b50909695505050505050565b801515811461073b575f80fd5b5f8060408385031215612bcf575f80fd5b8235612bda81612aed565b91506020830135612bea81612bb1565b809150509250929050565b5f8060208385031215612c06575f80fd5b82356001600160401b0380821115612c1c575f80fd5b818501915085601f830112612c2f575f80fd5b813581811115612c3d575f80fd5b86602060c083028501011115612c51575f80fd5b60209290920196919550909350505050565b5f805f60608486031215612c75575f80fd5b8335612c8081612aed565b95602085013595506040909401359392505050565b5f60208284031215612ca5575f80fd5b5035919050565b5f60c08284031215612cbc575f80fd5b50919050565b5f8060208385031215612cd3575f80fd5b82356001600160401b0380821115612ce9575f80fd5b818501915085601f830112612cfc575f80fd5b813581811115612d0a575f80fd5b866020606083028501011115612c51575f80fd5b80356001600160401b03811681146119ac575f80fd5b5f8060408385031215612d45575f80fd5b8235612d5081612aed565b9150612b5c60208401612d1e565b5f8083601f840112612d6e575f80fd5b5081356001600160401b03811115612d84575f80fd5b6020830191508360208260051b8501011115612d9e575f80fd5b9250929050565b5f805f805f8060608789031215612dba575f80fd5b86356001600160401b0380821115612dd0575f80fd5b612ddc8a838b01612d5e565b90985096506020890135915080821115612df4575f80fd5b612e008a838b01612d5e565b90965094506040890135915080821115612e18575f80fd5b50612e2589828a01612d5e565b979a9699509497509295939492505050565b5f815180845260208085019450602084015f5b83811015612e68578151151587529582019590820190600101612e4a565b509495945050505050565b604081525f612e856040830185612e37565b8281036020840152612e978185612e37565b95945050505050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f60018201612eed57612eed612ec8565b5060010190565b5f60208284031215612f04575f80fd5b610e4682612d1e565b5f60208284031215612f1d575f80fd5b8135610e4681612bb1565b634e487b7160e01b5f52602160045260245ffd5b5f60208284031215612f4c575f80fd5b813560038110610e46575f80fd5b5f60208284031215612f6a575f80fd5b610e4682612b1c565b80820180821115611a3d57611a3d612ec8565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190525f9061251890830184612f86565b5f60208284031215612fe7575f80fd5b8151610e4681612bb1565b6001600160a01b038481168252831660208201526060604082018190525f90612e9790830184612f86565b5f6020828403121561302d575f80fd5b5051919050565b6001600160801b0382811682821603908082111561150957611509612ec8565b8082028115828204841417611a3d57611a3d612ec8565b5f8261308557634e487b7160e01b5f52601260045260245ffd5b500490565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b81810381811115611a3d57611a3d612ec8565b5f602082840312156130f8575f80fd5b8151610e4681612aed56fe7365744173736574426f756e64656450726963696e67456e61626c656428616464726573732c626f6f6c29736574546f6b656e436f6e666967732828616464726573732c75696e7436342c75696e743235362c75696e743235362c626f6f6c2c626f6f6c295b5d297365745468726573686f6c647328616464726573732c75696e743235362c75696e7432353629736574546f6b656e436f6e6669672828616464726573732c75696e7436342c75696e743235362c75696e743235362c626f6f6c2c626f6f6c292973796e635072696365426f756e6473416e6450726f74656374696f6e732828616464726573732c75696e74382c75696e74323536295b5d29736574436f6f6c646f776e506572696f6428616464726573732c75696e74363429a264697066735822122007b4cdcf907b0609fb9db3ddc0f71466578874b106706d462bbd01df9551438864736f6c63430008190033", + "deployedBytecode": "0x608060405234801561000f575f80fd5b5060043610610255575f3560e01c806388142b6b11610140578063b540894f116100bf578063cf1412c011610084578063cf1412c0146106ba578063dcc88962146106cd578063e2201bb8146106e0578063e30c3978146106f3578063e40c2fed14610704578063f2fde38b14610717575f80fd5b8063b540894f1461055f578063b62e4c9214610580578063bd11c4c0146103fe578063c3821757146105a7578063c4d66de8146106a7575f80fd5b8063a31ebe5811610105578063a31ebe58146104e6578063a4edcd4c146104f9578063a9534f8a14610520578063a9c3cab11461053b578063b4a0bdf31461054e575f80fd5b806388142b6b146104755780638a2f7f6d146104885780638cf38bb1146104af5780638da5cb5b146104c2578063969f58d3146104d3575f80fd5b80634912c452116101d757806376489e381161019c57806376489e38146103b357806379ba5097146103f65780637f5e1328146103fe5780637fa6ea501461040c578063870dc597146104345780638769b23114610447575f80fd5b80634912c4521461034757806352a6bce31461035a578063578e5c221461036d5780636121316814610398578063715018a6146103ab575f80fd5b80631be74faf1161021d5780631be74faf146102f157806323013b83146103065780632f7b5dd7146103195780633b4ecdb21461032c578063412b6ec714610334575f80fd5b806308af5431146102595780630a5fa04d1461027b5780630e32cb86146102a25780631169d5a6146102b757806315a53122146102de575b5f80fd5b6102686706f05b59d3b2000081565b6040519081526020015b60405180910390f35b6102687f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb897060081565b6102b56102b0366004612b01565b61072a565b005b6102687f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf700081565b6102b56102ec366004612b32565b61073e565b6102f961078b565b6040516102729190612b65565b6102b5610314366004612bbe565b61093e565b6102b5610327366004612bf5565b610a6a565b6102f9610b33565b6102b5610342366004612bbe565b610b93565b6102b5610355366004612c63565b610c43565b610268610368366004612b01565b610e3c565b61038061037b366004612c95565b610e4d565b6040516001600160a01b039091168152602001610272565b6102b56103a6366004612cac565b610e75565b6102b5610ea6565b6103e66103c1366004612b01565b6001600160a01b03165f90815260c96020526040902060010154610100900460ff1690565b6040519015158152602001610272565b6102b5610eb9565b61026866b1a2bc2ec5000081565b61041f61041a366004612b01565b610f30565b60408051928352602083019190915201610272565b6102b5610442366004612cc2565b610f44565b6103e6610455366004612b01565b6001600160a01b03165f90815260c9602052604090206001015460ff1690565b61041f610483366004612b01565b6110af565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b6102b56104bd366004612b01565b6110ba565b6033546001600160a01b0316610380565b6102b56104e1366004612b32565b611101565b6102b56104f4366004612d34565b61114b565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b61038073bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb81565b6102b5610549366004612b01565b61121e565b6097546001600160a01b0316610380565b61057261056d366004612da5565b61122c565b604051610272929190612e73565b6103807f000000000000000000000000000000000000000000000000000000000000000081565b6106326105b5366004612b01565b60c96020525f9081526040902080546001820154600283015460038401546004909401546001600160801b0380851695600160801b9586900482169560ff8087169661010081048216966001600160401b03620100008304811697600160501b90930416956001600160a01b03909116948082169490041691168a565b604080516001600160801b039b8c168152998b1660208b01529715159789019790975294151560608801526001600160401b0393841660808801529290911660a08601526001600160a01b031660c0850152841660e08401529290921661010082015290151561012082015261014001610272565b6102b56106b5366004612b01565b6113f2565b6102686106c8366004612b01565b6114ff565b6103e66106db366004612b01565b611510565b6102686106ee366004612b01565b61159c565b6065546001600160a01b0316610380565b610268610712366004612b01565b6115a6565b6102b5610725366004612b01565b6115b0565b610732611621565b61073b8161167b565b50565b61077c6040518060400160405280601f81526020017f7570646174654d696e507269636528616464726573732c75696e743132382900815250611739565b61078782825f6117d0565b5050565b60ca546060905f816001600160401b038111156107aa576107aa612ea0565b6040519080825280602002602001820160405280156107d3578160200160208202803683370190505b5090505f805b838110156108995760c95f60ca83815481106107f7576107f7612eb4565b5f9182526020808320909101546001600160a01b0316835282019290925260400190206001015460ff61010090910416156108915760ca818154811061083f5761083f612eb4565b5f918252602090912001546001600160a01b0316838361085e81612edc565b94508151811061087057610870612eb4565b60200260200101906001600160a01b031690816001600160a01b0316815250505b6001016107d9565b505f816001600160401b038111156108b3576108b3612ea0565b6040519080825280602002602001820160405280156108dc578160200160208202803683370190505b5090505f5b82811015610935578381815181106108fb576108fb612eb4565b602002602001015182828151811061091557610915612eb4565b6001600160a01b03909216602092830291909101909101526001016108e1565b50949350505050565b61095f6040518060600160405280602b8152602001613104602b9139611739565b6109688261193d565b5f61097283611964565b9050811580156109865750600181015460ff165b156109b4576040516310ce5af160e11b81526001600160a01b03841660048201526024015b60405180910390fd5b8115158160010160019054906101000a900460ff161515036109d557505050565b8115610a07575f6109ed6109e8856119b1565b611a43565b90506109fa828583611a73565b610a05828583611ae3565b505b6001810180548315156101000261ff00199091161790556040516001600160a01b038416907fde52a1c70ed1ca343b25cca640873d949641bd6ec7a2c2d0e67374ce54ff89cd90610a5d90851515815260200190565b60405180910390a2505050565b610a8b6040518060600160405280603d815260200161312f603d9139611739565b805f819003610aad57604051634ec4810560e11b815260040160405180910390fd5b5f5b81811015610b2d5736848483818110610aca57610aca612eb4565b60c002919091019150610b249050610ae56020830183612b01565b610af56040840160208501612ef4565b60408401356060850135610b0f60a0870160808801612f0d565b610b1f60c0880160a08901612f0d565b611b57565b50600101610aaf565b50505050565b606060ca805480602002602001604051908101604052809291908181526020018280548015610b8957602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311610b6b575b5050505050905090565b610bd16040518060400160405280601f81526020017f73657443616368696e67456e61626c656428616464726573732c626f6f6c2900815250611739565b5f610bdb83611964565b60048101546040805160ff9092161515825284151560208301529192506001600160a01b038516917f80b87a057217c1d7743a43adf6fcf8a06553fde1205440ebbfb539f6f0e04424910160405180910390a2600401805460ff191691151591909117905550565b610c6460405180606001604052806026815260200161316c60269139611739565b610c6d8361193d565b610c768261200e565b610c7f8161200e565b66b1a2bc2ec50000821015610cb757604051630f4d736160e01b81526004810183905266b1a2bc2ec5000060248201526044016109ab565b6706f05b59d3b20000821115610cf1576040516350ddbb7560e11b8152600481018390526706f05b59d3b2000060248201526044016109ab565b818110610d1457604051632021db5560e21b8152600481018290526024016109ab565b5f610d1e84611964565b60038101549091506001600160801b03168314610da6576003810154604080516001600160801b039092168252602082018590526001600160a01b038616917f605baae98875f2133a40a8d489531e195b473e8cdb0c860a2d2b51d430eff588910160405180910390a26003810180546001600160801b0319166001600160801b0385161790555b6003810154600160801b90046001600160801b03168214610b2d57600381015460408051600160801b9092046001600160801b03168252602082018490526001600160a01b038616917f75c087963c80520a0d86e6333991c95d79462341f6960f2904396c23def23dd4910160405180910390a26003810180546001600160801b03808516600160801b02911617905550505050565b5f610e468261202e565b9392505050565b60ca8181548110610e5c575f80fd5b5f918252602090912001546001600160a01b0316905081565b610e966040518060600160405280603a8152602001613192603a9139611739565b61073b610ae56020830183612b01565b610eae611621565b610eb75f61210c565b565b60655433906001600160a01b03168114610f275760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b60648201526084016109ab565b61073b8161210c565b5f80610f3b8361202e565b91509150915091565b610f656040518060600160405280603881526020016131cc60389139611739565b805f5b81811015610b2d5736848483818110610f8357610f83612eb4565b6060029190910191505f9050610f9f6040830160208401612f3c565b6002811115610fb057610fb0612f28565b03610fdd57610fd8610fc56020830183612b01565b610fd28360400135611a43565b5f6117d0565b6110a6565b6001610fef6040830160208401612f3c565b600281111561100057611000612f28565b0361102957610fd86110156020830183612b01565b6110228360400135611a43565b60016117d0565b600261103b6040830160208401612f3c565b600281111561104c5761104c612f28565b0361106657610fd86110616020830183612b01565b612125565b6110766040820160208301612f3c565b600281111561108757611087612f28565b604051635374467d60e11b815260ff90911660048201526024016109ab565b50600101610f68565b5f80610f3b836122b9565b6110f86040518060400160405280601b81526020017f6578697450726f74656374696f6e4d6f64652861646472657373290000000000815250611739565b61073b81612125565b61113f6040518060400160405280601f81526020017f7570646174654d6178507269636528616464726573732c75696e743132382900815250611739565b610787828260016117d0565b61116c60405180606001604052806021815260200161320460219139611739565b6111758261193d565b611187816001600160401b031661200e565b5f61119183611964565b6001810154604080516001600160401b03600160501b9093048316815291851660208301529192506001600160a01b038516917f890c8f3ce148a0c50a68d44c085aa3228b8e72273a03dba4ecfd402f94033c1e910160405180910390a260010180546001600160401b03909216600160501b0267ffffffffffffffff60501b1990921691909117905550565b6112278161202e565b505050565b60608086858114158061123f5750808414155b1561125d57604051634ec4810560e11b815260040160405180910390fd5b806001600160401b0381111561127557611275612ea0565b60405190808252806020026020018201604052801561129e578160200160208202803683370190505b509250806001600160401b038111156112b9576112b9612ea0565b6040519080825280602002602001820160405280156112e2578160200160208202803683370190505b5091505f5b818110156113e5575f60c95f8c8c8581811061130557611305612eb4565b905060200201602081019061131a9190612b01565b6001600160a01b0316815260208101919091526040015f208054909150611370906001600160801b03168a8a8581811061135657611356612eb4565b905060200201602081019061136b9190612f5a565b6123f8565b85838151811061138257611382612eb4565b9115156020928302919091019091015280546113ba90600160801b90046001600160801b031688888581811061135657611356612eb4565b8483815181106113cc576113cc612eb4565b91151560209283029190910190910152506001016112e7565b5050965096945050505050565b5f54610100900460ff161580801561141057505f54600160ff909116105b806114295750303b15801561142957505f5460ff166001145b61148c5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016109ab565b5f805460ff1916600117905580156114ad575f805461ff0019166101001790555b6114b6826124a1565b8015610787575f805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498906020015b60405180910390a15050565b5f6115098261202e565b5092915050565b6001600160a01b0381165f90815260c960205260408120600181015460ff1680156115615750600181015461155d906001600160401b03600160501b820481169162010000900416612f73565b4210155b8015610e465750600381015481546001600160801b03600160801b928390048116926115949280831692919004166124d8565b109392505050565b5f610e46826122b9565b5f611509826122b9565b6115b8611621565b606580546001600160a01b0383166001600160a01b031990911681179091556115e96033546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6033546001600160a01b03163314610eb75760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016109ab565b6001600160a01b0381166116df5760405162461bcd60e51b815260206004820152602560248201527f696e76616c696420616365737320636f6e74726f6c206d616e61676572206164604482015264647265737360d81b60648201526084016109ab565b609780546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa091016114f3565b6097546040516318c5e8ab60e01b81525f916001600160a01b0316906318c5e8ab9061176b9033908690600401612fb4565b602060405180830381865afa158015611786573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117aa9190612fd7565b90508061078757333083604051634a3fa29360e01b81526004016109ab93929190612ff2565b6117d98361193d565b816001600160801b03165f036118025760405163a2b326dd60e01b815260040160405180910390fd5b5f61180c84611964565b90505f611818856119b1565b90505f83600181111561182d5761182d612f28565b036118af5781546001600160801b03600160801b9091048116908516118061185d575080846001600160801b0316115b1561189f5760405160016203590160e31b031981526001600160a01b03861660048201526001600160801b0385166024820152604481018290526064016109ab565b6118aa828686611a73565b611936565b60018360018111156118c3576118c3612f28565b036119365781546001600160801b0390811690851610806118ec575080846001600160801b0316105b1561192b57604051636df018fb60e11b81526001600160a01b03861660048201526001600160801b0385166024820152604481018290526064016109ab565b611936828686611ae3565b5050505050565b6001600160a01b03811661073b576040516342bcdf7f60e11b815260040160405180910390fd5b6001600160a01b038082165f90815260c96020526040902060028101549091166119ac576040516349c9b68960e11b81526001600160a01b03831660048201526024016109ab565b919050565b6040516341976e0960e01b81526001600160a01b0382811660048301525f917f0000000000000000000000000000000000000000000000000000000000000000909116906341976e0990602401602060405180830381865afa158015611a19573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a3d919061301d565b92915050565b5f6001600160801b03821115611a6f5760405163339718d960e11b8152600481018390526024016109ab565b5090565b8254604080516001600160801b03928316815291831660208301526001600160a01b038416917f532579a79f45c3e02b749f0817d46bab6b256e8d1dae660d2f432d8032f21f2d910160405180910390a282546001600160801b0319166001600160801b03919091161790915550565b825460408051600160801b9092046001600160801b039081168352831660208301526001600160a01b038416917f067a69521c594f269500a8d1fd306d6cade49e09512a3fdf637a16aa1694dd88910160405180910390a282546001600160801b03918216600160801b0291161790915550565b611b608661193d565b611b72856001600160401b031661200e565b611b7b8461200e565b611b848361200e565b6001600160a01b038681165f90815260c960205260409020600201541615611bca57604051630d8cdd9d60e01b81526001600160a01b03871660048201526024016109ab565b66b1a2bc2ec50000841015611c0257604051630f4d736160e01b81526004810185905266b1a2bc2ec5000060248201526044016109ab565b6706f05b59d3b20000841115611c3c576040516350ddbb7560e11b8152600481018590526706f05b59d3b2000060248201526044016109ab565b838310611c5f57604051632021db5560e21b8152600481018490526024016109ab565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316866001600160a01b031603611cb15760405163c992a64b60e01b815260040160405180910390fd5b5f611cbe6109e8886119b1565b9050604051806101400160405280826001600160801b03168152602001826001600160801b031681526020015f1515815260200184151581526020015f6001600160401b03168152602001876001600160401b03168152602001886001600160a01b03168152602001866001600160801b03168152602001856001600160801b0316815260200183151581525060c95f896001600160a01b03166001600160a01b031681526020019081526020015f205f820151815f015f6101000a8154816001600160801b0302191690836001600160801b031602179055506020820151815f0160106101000a8154816001600160801b0302191690836001600160801b031602179055506040820151816001015f6101000a81548160ff02191690831515021790555060608201518160010160016101000a81548160ff02191690831515021790555060808201518160010160026101000a8154816001600160401b0302191690836001600160401b0316021790555060a082015181600101600a6101000a8154816001600160401b0302191690836001600160401b0316021790555060c0820151816002015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555060e0820151816003015f6101000a8154816001600160801b0302191690836001600160801b031602179055506101008201518160030160106101000a8154816001600160801b0302191690836001600160801b03160217905550610120820151816004015f6101000a81548160ff02191690831515021790555090505060ca87908060018154018082558091505060019003905f5260205f20015f9091909190916101000a8154816001600160a01b0302191690836001600160a01b03160217905550866001600160a01b03167feaeb53cd979b528911d7ed793f11a25e44e5076bef70f458f60049acb4c322b682838989604051611fb894939291906001600160801b0394851681529290931660208301526001600160401b03166040820152606081019190915260800190565b60405180910390a2866001600160a01b03167fde52a1c70ed1ca343b25cca640873d949641bd6ec7a2c2d0e67374ce54ff89cd84604051611ffd911515815260200190565b60405180910390a250505050505050565b805f0361073b5760405163273e150360e21b815260040160405180910390fd5b5f805f61203a84612520565b90506120458161263e565b9093509150821580159061205857508115155b156120635750915091565b5f61206d826119b1565b6001600160a01b0383165f90815260c960205260409020600181015491925090610100900460ff166120af576120a48383846126c9565b509485945092505050565b5f805f6120bd848688612746565b9250925092505f6120d0858789856127c7565b90506120f08187866001600160801b0316866001600160801b03166128ce565b9099509750612100878a8a6126c9565b50505050505050915091565b606580546001600160a01b031916905561073b8161290b565b61212e8161193d565b5f61213882611964565b600181015490915060ff1661216b57604051638e001e1f60e01b81526001600160a01b03831660048201526024016109ab565b6001810154612192906001600160401b03600160501b820481169162010000900416612f73565b4210156121e7576001810154604051630874f3a160e01b81526001600160a01b03841660048201526001600160401b0362010000830481166024830152600160501b90920490911660448201526064016109ab565b80545f90612208906001600160801b0380821691600160801b9004166124d8565b6003830154909150600160801b90046001600160801b0316811061226c5760038201546040516311c9371560e01b81526001600160a01b038516600482015260248101839052600160801b9091046001600160801b031660448201526064016109ab565b60018201805469ffffffffffffffff00ff191690556040516001600160a01b038416907f6977cf3ab0aaccc6ceeb77ddaf93b2e1f1b9e6c7e39c7fb08314efbbea365741905f90a2505050565b5f805f6122c584612520565b90506122d08161263e565b909350915082158015906122e357508115155b156122ee5750915091565b5f6122f8826119b1565b6001600160a01b0383165f90815260c960205260409020600181015491925090610100900460ff1661232f57509485945092505050565b5f61233983611a43565b82549091505f906001600160801b0316841061235f5782546001600160801b0316612361565b815b83549091505f90600160801b90046001600160801b03168511612395578354600160801b90046001600160801b0316612397565b825b60018501549091505f9060ff16806123c7575060038501546123c7908790859085906001600160801b031661295c565b90506123e78187856001600160801b0316856001600160801b03166128ce565b909b909a5098505050505050505050565b5f6001600160801b038316158061241657506001600160801b038216155b1561242257505f611a3d565b5f826001600160801b0316846001600160801b031611612454576124468484613034565b6001600160801b0316612468565b61245e8385613034565b6001600160801b03165b905066b1a2bc2ec500006001600160801b03851661248e670de0b6b3a764000084613054565b612498919061306b565b11949350505050565b5f54610100900460ff166124c75760405162461bcd60e51b81526004016109ab9061308a565b6124cf6129db565b61073b81612a09565b5f806124f06001600160801b038086169085166130d5565b90506001600160801b03841661250e670de0b6b3a764000083613054565b612518919061306b565b949350505050565b5f61252a8261193d565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b03160361257e575073bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb919050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316826001600160a01b0316036125de57507f0000000000000000000000000000000000000000000000000000000000000000919050565b816001600160a01b0316636f307dc36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561261a573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a3d91906130e8565b6001600160a01b0381165f90815260c96020526040812060040154819060ff1661266c57505f928392509050565b6126967f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf700084612a2f565b91506126c27f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb897060084612a2f565b9050915091565b6001600160a01b0383165f90815260c9602052604090206004015460ff166126f057505050565b61271b7f7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf70008484612a77565b6112277f84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb89706008483612a77565b5f805f8061275386611a43565b87549091506001600160801b0380821691600160801b90048116905f90841683111561278b576127848a8986611a73565b5082915060015b816001600160801b0316846001600160801b031611156127b7576127b08a8986611ae3565b5082905060015b9199909850909650945050505050565b835460038501545f916127f39186916001600160801b0380821692600160801b9092048116911661295c565b156128b657600185015460ff1615808061280a5750825b156128355760018601805469ffffffffffffffff0000191662010000426001600160401b0316021790555b801561284c576001868101805460ff191690911790555b8554604080518781526001600160801b038084166020830152600160801b909304909216908201526001600160a01b038516907f0e25941e7a04e3bd937c28c403c9431e5fe5cf87df19322b5352bf457f8b1c5f9060600160405180910390a26001915050612518565b600185015460ff161561251857506001949350505050565b5f80856128df575083905080612902565b8385106128ec57836128ee565b845b8386116128fb57836128fd565b855b915091505b94509492505050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f80670de0b6b3a76400006129718482612f73565b612984906001600160801b038816613054565b61298e919061306b565b90505f670de0b6b3a76400006129a485826130d5565b6129b7906001600160801b038816613054565b6129c1919061306b565b9050818711806129d057508087105b979650505050505050565b5f54610100900460ff16612a015760405162461bcd60e51b81526004016109ab9061308a565b610eb7612abe565b5f54610100900460ff166107325760405162461bcd60e51b81526004016109ab9061308a565b5f808383604051602001612a569291909182526001600160a01b0316602082015260400190565b60408051601f1981840301815291905280516020909101205c949350505050565b5f8383604051602001612a9d9291909182526001600160a01b0316602082015260400190565b60405160208183030381529060405280519060200120905081815d50505050565b5f54610100900460ff16612ae45760405162461bcd60e51b81526004016109ab9061308a565b610eb73361210c565b6001600160a01b038116811461073b575f80fd5b5f60208284031215612b11575f80fd5b8135610e4681612aed565b80356001600160801b03811681146119ac575f80fd5b5f8060408385031215612b43575f80fd5b8235612b4e81612aed565b9150612b5c60208401612b1c565b90509250929050565b602080825282518282018190525f9190848201906040850190845b81811015612ba55783516001600160a01b031683529284019291840191600101612b80565b50909695505050505050565b801515811461073b575f80fd5b5f8060408385031215612bcf575f80fd5b8235612bda81612aed565b91506020830135612bea81612bb1565b809150509250929050565b5f8060208385031215612c06575f80fd5b82356001600160401b0380821115612c1c575f80fd5b818501915085601f830112612c2f575f80fd5b813581811115612c3d575f80fd5b86602060c083028501011115612c51575f80fd5b60209290920196919550909350505050565b5f805f60608486031215612c75575f80fd5b8335612c8081612aed565b95602085013595506040909401359392505050565b5f60208284031215612ca5575f80fd5b5035919050565b5f60c08284031215612cbc575f80fd5b50919050565b5f8060208385031215612cd3575f80fd5b82356001600160401b0380821115612ce9575f80fd5b818501915085601f830112612cfc575f80fd5b813581811115612d0a575f80fd5b866020606083028501011115612c51575f80fd5b80356001600160401b03811681146119ac575f80fd5b5f8060408385031215612d45575f80fd5b8235612d5081612aed565b9150612b5c60208401612d1e565b5f8083601f840112612d6e575f80fd5b5081356001600160401b03811115612d84575f80fd5b6020830191508360208260051b8501011115612d9e575f80fd5b9250929050565b5f805f805f8060608789031215612dba575f80fd5b86356001600160401b0380821115612dd0575f80fd5b612ddc8a838b01612d5e565b90985096506020890135915080821115612df4575f80fd5b612e008a838b01612d5e565b90965094506040890135915080821115612e18575f80fd5b50612e2589828a01612d5e565b979a9699509497509295939492505050565b5f815180845260208085019450602084015f5b83811015612e68578151151587529582019590820190600101612e4a565b509495945050505050565b604081525f612e856040830185612e37565b8281036020840152612e978185612e37565b95945050505050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f60018201612eed57612eed612ec8565b5060010190565b5f60208284031215612f04575f80fd5b610e4682612d1e565b5f60208284031215612f1d575f80fd5b8135610e4681612bb1565b634e487b7160e01b5f52602160045260245ffd5b5f60208284031215612f4c575f80fd5b813560038110610e46575f80fd5b5f60208284031215612f6a575f80fd5b610e4682612b1c565b80820180821115611a3d57611a3d612ec8565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b6001600160a01b03831681526040602082018190525f9061251890830184612f86565b5f60208284031215612fe7575f80fd5b8151610e4681612bb1565b6001600160a01b038481168252831660208201526060604082018190525f90612e9790830184612f86565b5f6020828403121561302d575f80fd5b5051919050565b6001600160801b0382811682821603908082111561150957611509612ec8565b8082028115828204841417611a3d57611a3d612ec8565b5f8261308557634e487b7160e01b5f52601260045260245ffd5b500490565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b81810381811115611a3d57611a3d612ec8565b5f602082840312156130f8575f80fd5b8151610e4681612aed56fe7365744173736574426f756e64656450726963696e67456e61626c656428616464726573732c626f6f6c29736574546f6b656e436f6e666967732828616464726573732c75696e7436342c75696e743235362c75696e743235362c626f6f6c2c626f6f6c295b5d297365745468726573686f6c647328616464726573732c75696e743235362c75696e7432353629736574546f6b656e436f6e6669672828616464726573732c75696e7436342c75696e743235362c75696e743235362c626f6f6c2c626f6f6c292973796e635072696365426f756e6473416e6450726f74656374696f6e732828616464726573732c75696e74382c75696e74323536295b5d29736574436f6f6c646f776e506572696f6428616464726573732c75696e74363429a264697066735822122007b4cdcf907b0609fb9db3ddc0f71466578874b106706d462bbd01df9551438864736f6c63430008190033", + "devdoc": { + "author": "Venus", + "events": { + "Initialized(uint8)": { + "details": "Triggered when the contract has been initialized or reinitialized." + } + }, + "kind": "dev", + "methods": { + "acceptOwnership()": { + "details": "The new owner accepts the ownership transfer." + }, + "canExitProtection(address)": { + "details": "Returns true when both conditions are met: 1. Cooldown period has elapsed since last trigger 2. Price range has converged below exit threshold", + "params": { + "asset": "The underlying asset address" + }, + "returns": { + "_0": "True if protection can be disabled" + } + }, + "checkAndGetWindowDrift(address[],uint128[],uint128[])": { + "custom:error": "InvalidArrayLength if the input array lengths do not match", + "details": "Allows the keeper to identify stale windows in a single call, avoiding N individual reads. Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)", + "params": { + "assets": "Array of asset addresses to check", + "proposedMaxs": "Keeper's off-chain window maximum prices", + "proposedMins": "Keeper's off-chain window minimum prices" + }, + "returns": { + "needsMaxUpdate": "Whether maxPrice drift exceeds deadband for each asset", + "needsMinUpdate": "Whether minPrice drift exceeds deadband for each asset" + } + }, + "constructor": { + "custom:oz-upgrades-unsafe-allow": "constructor", + "params": { + "_resilientOracle": "Address of the ResilientOracle contract", + "nativeMarketAddress": "The address of a native market (for bsc it would be vBNB address)", + "vaiAddress": "The address of the VAI token, or address(0) if VAI is not deployed on the chain." + } + }, + "currentlyUsingProtectedPrice(address)": { + "params": { + "asset": "The underlying asset address" + }, + "returns": { + "_0": "True if the asset is currently using the protected price instead of spot" + } + }, + "exitProtectionMode(address)": { + "custom:access": "Only authorized monitor/keeper addresses", + "custom:error": "ProtectedPriceInactive if protection is not currently activeCooldownNotElapsed if cooldown period has not elapsedPriceRangeNotConverged if window range is still above exit threshold", + "custom:event": "ProtectionModeExited", + "details": "Called by the keeper/monitor after confirming price has normalised. Enforces two conditions on-chain: 1. Cooldown period has elapsed since the last trigger 2. Price range has converged below the exit threshold", + "params": { + "asset": "The underlying asset address" + } + }, + "getAllBoundedPricingEnabledAssets()": { + "details": "Iterates the append-only allAssets array and filters by isBoundedPricingEnabled. Gas-free for off-chain callers.", + "returns": { + "_0": "result Array of whitelisted asset addresses" + } + }, + "getBoundedCollateralPrice(address)": { + "custom:event": "MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold", + "details": "Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns the conservative (lower) price when protection is active. Used by keepers or direct callers who want atomic update + read.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "collateralPrice": "The bounded collateral price" + } + }, + "getBoundedCollateralPriceView(address)": { + "details": "Reads from transient cache first when the asset's `cachingEnabled` flag is `true` (populated by a prior updateProtectionState call in the same transaction). Falls back to ResilientOracle on cache miss or when caching is disabled. Returns min(spot, windowMin) when protection is active, spot otherwise.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "collateralPrice": "The bounded collateral price" + } + }, + "getBoundedDebtPrice(address)": { + "custom:event": "MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold", + "details": "Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns the conservative (higher) price when protection is active. Used by keepers or direct callers who want atomic update + read.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "debtPrice": "The bounded debt price" + } + }, + "getBoundedDebtPriceView(address)": { + "details": "Reads from transient cache first when the asset's `cachingEnabled` flag is `true` (populated by a prior updateProtectionState call in the same transaction). Falls back to ResilientOracle on cache miss or when caching is disabled. Returns max(spot, windowMax) when protection is active, spot otherwise.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "debtPrice": "The bounded debt price" + } + }, + "getBoundedPrices(address)": { + "custom:event": "MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold", + "details": "Fetches spot from ResilientOracle, updates the price window, checks trigger, and returns both conservative prices in a single call.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "collateralPrice": "The bounded collateral price", + "debtPrice": "The bounded debt price" + } + }, + "getBoundedPricesView(address)": { + "details": "Reads from transient cache first when the asset's `cachingEnabled` flag is `true`; falls back to ResilientOracle on cache miss or when caching is disabled.", + "params": { + "vToken": "vToken address" + }, + "returns": { + "collateralPrice": "The bounded collateral price", + "debtPrice": "The bounded debt price" + } + }, + "getInitializedAssets()": { + "returns": { + "_0": "Array of all initialized asset addresses" + } + }, + "initialize(address)": { + "params": { + "accessControlManager_": "Address of the access control manager contract" + } + }, + "isBoundedPricingEnabled(address)": { + "params": { + "asset": "The underlying asset address" + }, + "returns": { + "_0": "True if the asset is whitelisted" + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "pendingOwner()": { + "details": "Returns the address of the pending owner." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "setAccessControlManager(address)": { + "custom:access": "Only Governance", + "custom:event": "Emits NewAccessControlManager event", + "details": "Admin function to set address of AccessControlManager", + "params": { + "accessControlManager_": "The new address of the AccessControlManager" + } + }, + "setAssetBoundedPricingEnabled(address,bool)": { + "custom:access": "Only Governance", + "custom:error": "ProtectedPriceActive if trying to disable an asset while protection is active", + "custom:event": "BoundedPricingWhitelistUpdated", + "params": { + "asset": "The underlying asset address", + "enabled": "Whether bounded pricing should be enabled for the asset" + } + }, + "setCachingEnabled(address,bool)": { + "custom:access": "Only Governance", + "custom:error": "MarketNotInitialized if the asset has not been initialized", + "custom:event": "CachingEnabledUpdated", + "details": "When disabled, each view/non-view price call recomputes bounded prices from the live spot instead of reading or writing the transient slots. The initial value is set via the `enableCaching` argument of `setTokenConfig`.", + "params": { + "asset": "The underlying asset address", + "enabled": "Whether transient caching is enabled for this asset" + } + }, + "setCooldownPeriod(address,uint64)": { + "custom:access": "Only Governance", + "custom:event": "CooldownPeriodSet", + "params": { + "asset": "The underlying asset address", + "newCooldown": "The new cooldown period in seconds" + } + }, + "setThresholds(address,uint256,uint256)": { + "custom:access": "Only Governance", + "custom:error": "ThresholdBelowMinimum if newTriggerThreshold is below 5%ThresholdAboveMaximum if newTriggerThreshold is above 50%InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold", + "custom:event": "TriggerThresholdSet if the trigger threshold changedResetThresholdSet if the reset threshold changed", + "params": { + "asset": "The underlying asset address", + "newResetThreshold": "The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.", + "newTriggerThreshold": "The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold." + } + }, + "setTokenConfig((address,uint64,uint256,uint256,bool,bool))": { + "custom:access": "Only Governance", + "custom:event": "ProtectionInitializedBoundedPricingWhitelistUpdated", + "params": { + "tokenConfig_": "Token config input for the asset" + } + }, + "setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])": { + "custom:access": "Only Governance", + "custom:error": "InvalidArrayLength if the input array is empty", + "custom:event": "ProtectionInitialized for each assetBoundedPricingWhitelistUpdated for each asset", + "params": { + "tokenConfigs_": "Array of token config inputs, one per asset" + } + }, + "syncPriceBoundsAndProtections((address,uint8,uint256)[])": { + "custom:access": "Only authorized keeper addresses", + "custom:error": "InvalidKeeperAction if an item carries an unsupported action enum value", + "custom:event": "MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited", + "details": "Each item is processed in array order; any item revert rolls back the whole batch. `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode. Empty `actions` is a no-op success.", + "params": { + "actions": "The list of keeper actions to apply" + } + }, + "transferOwnership(address)": { + "details": "Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner." + }, + "updateMaxPrice(address,uint128)": { + "custom:access": "Only authorized keeper addresses", + "custom:event": "MaxPriceUpdated", + "details": "Called by the keeper to push corrected max values from the off-chain sliding window. Constraint: newMax must be at or above the current spot price.", + "params": { + "asset": "The underlying asset address", + "newMax": "The new maximum price" + } + }, + "updateMinPrice(address,uint128)": { + "custom:access": "Only authorized keeper addresses", + "custom:event": "MinPriceUpdated", + "details": "Called by the keeper to push corrected min values from the off-chain sliding window. Constraint: newMin must be at or below the current spot price.", + "params": { + "asset": "The underlying asset address", + "newMin": "The new minimum price" + } + }, + "updateProtectionState(address)": { + "custom:event": "MinPriceUpdated if a new window minimum is recordedMaxPriceUpdated if a new window maximum is recordedProtectionTriggered if the spot price deviates beyond the threshold", + "details": "Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before liquidity calculations). Subsequent calls to getBoundedCollateralPriceView / getBoundedDebtPriceView within the same transaction will read from the transient cache instead of querying ResilientOracle again, keeping those functions as `view` and avoiding redundant oracle calls. The transient cache is only populated when the asset's `cachingEnabled` flag is `true`. When caching is disabled, view price reads fall through to live recomputation. Permissionless: anyone can call this, both for gas optimisation and to ensure every caller in the same transaction reads the correct, up-to-date bounded price.", + "params": { + "vToken": "vToken address" + } + } + }, + "stateVariables": { + "COLLATERAL_PRICE_CACHE_SLOT": { + "details": "custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/collateralCache\")) - 1)) & ~bytes32(uint256(0xff))" + }, + "DEBT_PRICE_CACHE_SLOT": { + "details": "custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\")) - 1)) & ~bytes32(uint256(0xff))" + }, + "RESILIENT_ORACLE": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + }, + "nativeMarket": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + }, + "vai": { + "custom:oz-upgrades-unsafe-allow": "state-variable-immutable" + } + }, + "title": "DeviationBoundedOracle", + "version": 1 + }, + "userdoc": { + "errors": { + "CooldownNotElapsed(address,uint64,uint64)": [ + { + "notice": "Thrown when trying to disable protection before cooldown has elapsed" + } + ], + "InvalidArrayLength()": [ + { + "notice": "Thrown when the lengths of the arrays are not equal" + } + ], + "InvalidKeeperAction(uint8)": [ + { + "notice": "Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value" + } + ], + "InvalidMaxPrice(address,uint128,uint256)": [ + { + "notice": "Thrown when keeper tries to set maxPrice below current spot" + } + ], + "InvalidMinPrice(address,uint128,uint256)": [ + { + "notice": "Thrown when keeper tries to set minPrice above current spot" + } + ], + "InvalidResetThreshold(uint256)": [ + { + "notice": "Thrown when the exit threshold is set at or above the trigger threshold" + } + ], + "MarketAlreadyInitialized(address)": [ + { + "notice": "Thrown when trying to initialize an already initialized market" + } + ], + "MarketNotInitialized(address)": [ + { + "notice": "Thrown when trying to use or update protection for an asset that has not been initialized" + } + ], + "PriceExceedsUint128(uint256)": [ + { + "notice": "Thrown when a price exceeds uint128 max" + } + ], + "PriceRangeNotConverged(address,uint256,uint256)": [ + { + "notice": "Thrown when trying to disable protection before price range has converged" + } + ], + "ProtectedPriceActive(address)": [ + { + "notice": "Thrown when trying to disable bounded pricing for an asset while protection is active" + } + ], + "ProtectedPriceInactive(address)": [ + { + "notice": "Thrown when trying to disable protection that is not active" + } + ], + "ThresholdAboveMaximum(uint256,uint256)": [ + { + "notice": "Thrown when threshold is set above the maximum allowed value" + } + ], + "ThresholdBelowMinimum(uint256,uint256)": [ + { + "notice": "Thrown when threshold is set below the minimum allowed value" + } + ], + "Unauthorized(address,address,string)": [ + { + "notice": "Thrown when the action is prohibited by AccessControlManager" + } + ], + "VAINotAllowed()": [ + { + "notice": "Thrown when trying to initialize protection for VAI" + } + ], + "ZeroAddressNotAllowed()": [ + { + "notice": "Thrown if the supplied address is a zero address where it is not allowed" + } + ], + "ZeroPriceNotAllowed()": [ + { + "notice": "Thrown when a zero price is provided where a non-zero price is required" + } + ], + "ZeroValueNotAllowed()": [ + { + "notice": "Thrown if the supplied value is 0 where it is not allowed" + } + ] + }, + "events": { + "BoundedPricingWhitelistUpdated(address,bool)": { + "notice": "Emitted when an asset's whitelist status changes" + }, + "CachingEnabledUpdated(address,bool,bool)": { + "notice": "Emitted when the per-asset transient caching flag is toggled" + }, + "CooldownPeriodSet(address,uint64,uint64)": { + "notice": "Emitted when the cooldown period is updated for an asset" + }, + "MaxPriceUpdated(address,uint128,uint128)": { + "notice": "Emitted when the keeper updates the maximum price for an asset" + }, + "MinPriceUpdated(address,uint128,uint128)": { + "notice": "Emitted when the keeper updates the minimum price for an asset" + }, + "NewAccessControlManager(address,address)": { + "notice": "Emitted when access control manager contract address is changed" + }, + "ProtectionInitialized(address,uint128,uint128,uint64,uint256)": { + "notice": "Emitted when protection is initialized for an asset" + }, + "ProtectionModeExited(address)": { + "notice": "Emitted when protection mode is disabled for an asset" + }, + "ProtectionTriggered(address,uint256,uint128,uint128)": { + "notice": "Emitted when protection mode is triggered for an asset" + }, + "ResetThresholdSet(address,uint256,uint256)": { + "notice": "Emitted when the exit threshold is updated for an asset" + }, + "TriggerThresholdSet(address,uint256,uint256)": { + "notice": "Emitted when the entry threshold is updated for an asset" + } + }, + "kind": "user", + "methods": { + "COLLATERAL_PRICE_CACHE_SLOT()": { + "notice": "Transient storage slot for caching final collateral prices within a transaction" + }, + "DEBT_PRICE_CACHE_SLOT()": { + "notice": "Transient storage slot for caching final debt prices within a transaction" + }, + "KEEPER_DEADBAND()": { + "notice": "Keeper deadband threshold (5%) — min/max corrections below this are suppressed" + }, + "MAX_THRESHOLD()": { + "notice": "Maximum allowed threshold value (50%)" + }, + "MIN_THRESHOLD()": { + "notice": "Minimum allowed threshold value (5%) to account for keeper deadband" + }, + "NATIVE_TOKEN_ADDR()": { + "notice": "Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc) and can serve as any underlying asset of a market that supports native tokens" + }, + "RESILIENT_ORACLE()": { + "notice": "Resilient Oracle used to fetch spot prices" + }, + "accessControlManager()": { + "notice": "Returns the address of the access control manager contract" + }, + "allAssets(uint256)": { + "notice": "Append-only array of all assets ever initialized, used for enumeration" + }, + "assetProtectionConfig(address)": { + "notice": "Per-asset protection state" + }, + "canExitProtection(address)": { + "notice": "Checks if protection can be exited for an asset" + }, + "checkAndGetWindowDrift(address[],uint128[],uint128[])": { + "notice": "Batch-checks which assets' on-chain min/max have drifted beyond the deadband from the keeper's proposed window values" + }, + "constructor": { + "notice": "Constructor for the implementation contract. Sets immutable variables." + }, + "currentlyUsingProtectedPrice(address)": { + "notice": "Checks if the asset is currently using the protected (bounded) price" + }, + "exitProtectionMode(address)": { + "notice": "Exits protection mode for a given asset" + }, + "getAllBoundedPricingEnabledAssets()": { + "notice": "Returns all currently whitelisted asset addresses" + }, + "getBoundedCollateralPrice(address)": { + "notice": "Gets the bounded collateral price for a given vToken, updating protection state" + }, + "getBoundedCollateralPriceView(address)": { + "notice": "Gets the bounded collateral price for a given vToken (view variant)" + }, + "getBoundedDebtPrice(address)": { + "notice": "Gets the bounded debt price for a given vToken, updating protection state" + }, + "getBoundedDebtPriceView(address)": { + "notice": "Gets the bounded debt price for a given vToken (view variant)" + }, + "getBoundedPrices(address)": { + "notice": "Gets both the bounded collateral and debt prices for a given vToken, updating protection state" + }, + "getBoundedPricesView(address)": { + "notice": "Gets both the bounded collateral and debt prices for a given vToken (view variant)" + }, + "getInitializedAssets()": { + "notice": "Returns all asset addresses that have ever been initialized" + }, + "initialize(address)": { + "notice": "Initializes the contract admin" + }, + "isBoundedPricingEnabled(address)": { + "notice": "Checks if an asset is whitelisted for bounded pricing" + }, + "nativeMarket()": { + "notice": "Native market address" + }, + "setAccessControlManager(address)": { + "notice": "Sets the address of AccessControlManager" + }, + "setAssetBoundedPricingEnabled(address,bool)": { + "notice": "Sets whether an asset is enabled for bounded pricing" + }, + "setCachingEnabled(address,bool)": { + "notice": "Toggles transient caching of the bounded (collateral, debt) pair for an asset" + }, + "setCooldownPeriod(address,uint64)": { + "notice": "Sets the cooldown period for an asset" + }, + "setThresholds(address,uint256,uint256)": { + "notice": "Sets the trigger and reset thresholds for an asset" + }, + "setTokenConfig((address,uint64,uint256,uint256,bool,bool))": { + "notice": "Initializes protection for a new asset" + }, + "setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])": { + "notice": "Batch-initializes protection for multiple assets in a single transaction" + }, + "syncPriceBoundsAndProtections((address,uint8,uint256)[])": { + "notice": "Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check" + }, + "updateMaxPrice(address,uint128)": { + "notice": "Updates the maximum price in the rolling window for a given asset" + }, + "updateMinPrice(address,uint128)": { + "notice": "Updates the minimum price in the rolling window for a given asset" + }, + "updateProtectionState(address)": { + "notice": "Fetches the spot price, updates the protection window, and caches the resolved collateral and debt prices in transient storage for the duration of the transaction." + }, + "vai()": { + "notice": "VAI address" + } + }, + "notice": "The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations. It maintains a per-market rolling min/max price window. When the current spot price deviates significantly from the window bounds, protection mode activates automatically and conservative pricing kicks in: - Collateral is valued at min(spot, windowMin) — caps collateral value at recent window low - Debt is valued at max(spot, windowMax) — floors debt value at recent window high This protects against instantaneous or short-duration price manipulation attacks on low-liquidity collateral tokens. Sustained attacks beyond the window period are expected to be handled by off-chain monitoring systems. The oracle exposes both view and non-view price functions. The non-view variants update the price window and trigger protection. The view variants read stored state only. A transient price cache avoids redundant ResilientOracle calls within the same transaction when updateProtectionState is called before the view price reads.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 349, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8" + }, + { + "astId": 352, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 1019, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage" + }, + { + "astId": 221, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address" + }, + { + "astId": 341, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 114, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_pendingOwner", + "offset": 0, + "slot": "101", + "type": "t_address" + }, + { + "astId": 208, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 1940, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "_accessControlManager", + "offset": 0, + "slot": "151", + "type": "t_contract(IAccessControlManagerV8)2125" + }, + { + "astId": 1945, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage" + }, + { + "astId": 2242, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "assetProtectionConfig", + "offset": 0, + "slot": "201", + "type": "t_mapping(t_address,t_struct(MarketProtectionState)5622_storage)" + }, + { + "astId": 2246, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "allAssets", + "offset": 0, + "slot": "202", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 2251, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "__gap", + "offset": 0, + "slot": "203", + "type": "t_array(t_uint256)48_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)48_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(IAccessControlManagerV8)2125": { + "encoding": "inplace", + "label": "contract IAccessControlManagerV8", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_struct(MarketProtectionState)5622_storage)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => struct IDeviationBoundedOracle.MarketProtectionState)", + "numberOfBytes": "32", + "value": "t_struct(MarketProtectionState)5622_storage" + }, + "t_struct(MarketProtectionState)5622_storage": { + "encoding": "inplace", + "label": "struct IDeviationBoundedOracle.MarketProtectionState", + "members": [ + { + "astId": 5594, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "minPrice", + "offset": 0, + "slot": "0", + "type": "t_uint128" + }, + { + "astId": 5597, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "maxPrice", + "offset": 16, + "slot": "0", + "type": "t_uint128" + }, + { + "astId": 5600, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "currentlyUsingProtectedPrice", + "offset": 0, + "slot": "1", + "type": "t_bool" + }, + { + "astId": 5603, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "isBoundedPricingEnabled", + "offset": 1, + "slot": "1", + "type": "t_bool" + }, + { + "astId": 5606, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "lastProtectionTriggeredAt", + "offset": 2, + "slot": "1", + "type": "t_uint64" + }, + { + "astId": 5609, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "cooldownPeriod", + "offset": 10, + "slot": "1", + "type": "t_uint64" + }, + { + "astId": 5612, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "asset", + "offset": 0, + "slot": "2", + "type": "t_address" + }, + { + "astId": 5615, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "triggerThreshold", + "offset": 0, + "slot": "3", + "type": "t_uint128" + }, + { + "astId": 5618, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "resetThreshold", + "offset": 16, + "slot": "3", + "type": "t_uint128" + }, + { + "astId": 5621, + "contract": "contracts/DeviationBoundedOracle.sol:DeviationBoundedOracle", + "label": "cachingEnabled", + "offset": 0, + "slot": "4", + "type": "t_bool" + } + ], + "numberOfBytes": "160" + }, + "t_uint128": { + "encoding": "inplace", + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "encoding": "inplace", + "label": "uint8", + "numberOfBytes": "1" + } + } + } +} diff --git a/deployments/bsctestnet/DeviationBoundedOracle_Proxy.json b/deployments/bsctestnet/DeviationBoundedOracle_Proxy.json new file mode 100644 index 00000000..551eaaab --- /dev/null +++ b/deployments/bsctestnet/DeviationBoundedOracle_Proxy.json @@ -0,0 +1,262 @@ +{ + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "receipt": { + "to": null, + "from": "0x4cD6300F5cb8D6BbA5E646131c3522664C10dF11", + "contractAddress": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "transactionIndex": 0, + "gasUsed": "589317", + "logsBloom": "0x00000000000000000000000000000000400000000000000000800000000000000000000000000000000000000020000000000000000000000000000000008000000000000000000000000000000002000001000000000000000000000000000000000000020000000000000000000800000000800000000000000000000000400000000000000000000000000002000000100000000080000000000000800000000000000000000000000000080440000000000000800000000000000000000000000020008000000000000000040000000000000400000000000000200020001000000000000000000000000000000000000880000000000000000000000000", + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32", + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "logs": [ + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x00000000000000000000000028f9576ec8d73331cda7f0a6fac88b0ca4d41e3f" + ], + "data": "0x", + "logIndex": 0, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + }, + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000004cd6300f5cb8d6bba5e646131c3522664c10df11" + ], + "data": "0x", + "logIndex": 1, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + }, + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": ["0x66fd58e82f7b31a2a5c30e0888f3093efe4e111b00cd2b0c31fe014601293aa0"], + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045f8a08f534f34a97187626e05d4b6648eeaa9aa", + "logIndex": 2, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + }, + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": ["0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 3, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + }, + { + "transactionIndex": 0, + "blockNumber": 102770929, + "transactionHash": "0xdaf70e4f278b5bc496fd14f91f26044bb85d4772d1f7f1b954a60040e81e94a4", + "address": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "topics": ["0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f"], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ef480a5654b231ff7d80a0681f938f3db71a6ca6", + "logIndex": 4, + "blockHash": "0x018962ed107006d4fcad6ef22d2b2b9e0b56ab825637934a82f147c1bca15c32" + } + ], + "blockNumber": 102770929, + "cumulativeGasUsed": "589317", + "status": 1, + "byzantium": true + }, + "args": [ + "0x28f9576ec8D73331CDa7F0A6fAc88b0cA4D41e3f", + "0xef480a5654b231ff7d80A0681F938f3Db71a6Ca6", + "0xc4d66de800000000000000000000000045f8a08f534f34a97187626e05d4b6648eeaa9aa" + ], + "numDeployments": 1, + "solcInputHash": "fad126030085a626952462c92d5fc265", + "metadata": "{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_logic\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"beacon\",\"type\":\"address\"}],\"name\":\"BeaconUpgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"admin_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"implementation_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"details\":\"This contract implements a proxy that is upgradeable by an admin. 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.\",\"events\":{\"AdminChanged(address,address)\":{\"details\":\"Emitted when the admin account has changed.\"},\"BeaconUpgraded(address)\":{\"details\":\"Emitted when the beacon is upgraded.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"admin()\":{\"details\":\"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`\"},\"constructor\":{\"details\":\"Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\"},\"implementation()\":{\"details\":\"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`\"},\"upgradeTo(address)\":{\"details\":\"Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\"},\"upgradeToAndCall(address,bytes)\":{\"details\":\"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}.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol\":\"OptimizedTransparentUpgradeableProxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"hardhat-deploy/solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\\n * proxy whose upgrades are fully controlled by the current implementation.\\n */\\ninterface IERC1822Proxiable {\\n /**\\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\\n * address.\\n *\\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\\n * function revert if invoked through a proxy.\\n */\\n function proxiableUUID() external view returns (bytes32);\\n}\\n\",\"keccak256\":\"0x93b4e21c931252739a1ec13ea31d3d35a5c068be3163ccab83e4d70c40355f03\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Proxy.sol\\\";\\nimport \\\"./ERC1967Upgrade.sol\\\";\\n\\n/**\\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\\n * implementation address that can be changed. This address is stored in storage in the location specified by\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\\n * implementation behind the proxy.\\n */\\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\\n /**\\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\\n *\\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\\n */\\n constructor(address _logic, bytes memory _data) payable {\\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.implementation\\\")) - 1));\\n _upgradeToAndCall(_logic, _data, false);\\n }\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _implementation() internal view virtual override returns (address impl) {\\n return ERC1967Upgrade._getImplementation();\\n }\\n}\\n\",\"keccak256\":\"0x6309f9f39dc6f4f45a24f296543867aa358e32946cd6b2874627a996d606b3a0\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../beacon/IBeacon.sol\\\";\\nimport \\\"../../interfaces/draft-IERC1822.sol\\\";\\nimport \\\"../../utils/Address.sol\\\";\\nimport \\\"../../utils/StorageSlot.sol\\\";\\n\\n/**\\n * @dev This abstract contract provides getters and event emitting update functions for\\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\\n *\\n * _Available since v4.1._\\n *\\n * @custom:oz-upgrades-unsafe-allow delegatecall\\n */\\nabstract contract ERC1967Upgrade {\\n // This is the keccak-256 hash of \\\"eip1967.proxy.rollback\\\" subtracted by 1\\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\\n\\n /**\\n * @dev Storage slot with the address of the current implementation.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.implementation\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n\\n /**\\n * @dev Emitted when the implementation is upgraded.\\n */\\n event Upgraded(address indexed implementation);\\n\\n /**\\n * @dev Returns the current implementation address.\\n */\\n function _getImplementation() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 implementation slot.\\n */\\n function _setImplementation(address newImplementation) private {\\n require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n }\\n\\n /**\\n * @dev Perform implementation upgrade\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeTo(address newImplementation) internal {\\n _setImplementation(newImplementation);\\n emit Upgraded(newImplementation);\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCall(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _upgradeTo(newImplementation);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(newImplementation, data);\\n }\\n }\\n\\n /**\\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\\n *\\n * Emits an {Upgraded} event.\\n */\\n function _upgradeToAndCallUUPS(\\n address newImplementation,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n // Upgrades from old implementations will perform a rollback test. This test requires the new\\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\\n // this special case will break upgrade paths from old UUPS implementation to new ones.\\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\\n _setImplementation(newImplementation);\\n } else {\\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\\n require(slot == _IMPLEMENTATION_SLOT, \\\"ERC1967Upgrade: unsupported proxiableUUID\\\");\\n } catch {\\n revert(\\\"ERC1967Upgrade: new implementation is not UUPS\\\");\\n }\\n _upgradeToAndCall(newImplementation, data, forceCall);\\n }\\n }\\n\\n /**\\n * @dev Storage slot with the admin of the contract.\\n * This is the keccak-256 hash of \\\"eip1967.proxy.admin\\\" subtracted by 1, and is\\n * validated in the constructor.\\n */\\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\\n\\n /**\\n * @dev Emitted when the admin account has changed.\\n */\\n event AdminChanged(address previousAdmin, address newAdmin);\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _getAdmin() internal view virtual returns (address) {\\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new address in the EIP1967 admin slot.\\n */\\n function _setAdmin(address newAdmin) private {\\n require(newAdmin != address(0), \\\"ERC1967: new admin is the zero address\\\");\\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\\n }\\n\\n /**\\n * @dev Changes the admin of the proxy.\\n *\\n * Emits an {AdminChanged} event.\\n */\\n function _changeAdmin(address newAdmin) internal {\\n emit AdminChanged(_getAdmin(), newAdmin);\\n _setAdmin(newAdmin);\\n }\\n\\n /**\\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\\n */\\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\\n\\n /**\\n * @dev Emitted when the beacon is upgraded.\\n */\\n event BeaconUpgraded(address indexed beacon);\\n\\n /**\\n * @dev Returns the current beacon.\\n */\\n function _getBeacon() internal view returns (address) {\\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\\n }\\n\\n /**\\n * @dev Stores a new beacon in the EIP1967 beacon slot.\\n */\\n function _setBeacon(address newBeacon) private {\\n require(Address.isContract(newBeacon), \\\"ERC1967: new beacon is not a contract\\\");\\n require(Address.isContract(IBeacon(newBeacon).implementation()), \\\"ERC1967: beacon implementation is not a contract\\\");\\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\\n }\\n\\n /**\\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\\n *\\n * Emits a {BeaconUpgraded} event.\\n */\\n function _upgradeBeaconToAndCall(\\n address newBeacon,\\n bytes memory data,\\n bool forceCall\\n ) internal {\\n _setBeacon(newBeacon);\\n emit BeaconUpgraded(newBeacon);\\n if (data.length > 0 || forceCall) {\\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x17668652127feebed0ce8d9431ef95ccc8c4292f03e3b8cf06c6ca16af396633\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/Proxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\\n * be specified by overriding the virtual {_implementation} function.\\n *\\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\\n * different contract through the {_delegate} function.\\n *\\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\\n */\\nabstract contract Proxy {\\n /**\\n * @dev Delegates the current call to `implementation`.\\n *\\n * This function does not return to its internal call site, it will return directly to the external caller.\\n */\\n function _delegate(address implementation) internal virtual {\\n assembly {\\n // Copy msg.data. We take full control of memory in this inline assembly\\n // block because it will not return to Solidity code. We overwrite the\\n // Solidity scratch pad at memory position 0.\\n calldatacopy(0, 0, calldatasize())\\n\\n // Call the implementation.\\n // out and outsize are 0 because we don't know the size yet.\\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\\n\\n // Copy the returned data.\\n returndatacopy(0, 0, returndatasize())\\n\\n switch result\\n // delegatecall returns 0 on error.\\n case 0 {\\n revert(0, returndatasize())\\n }\\n default {\\n return(0, returndatasize())\\n }\\n }\\n }\\n\\n /**\\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\\n * and {_fallback} should delegate.\\n */\\n function _implementation() internal view virtual returns (address);\\n\\n /**\\n * @dev Delegates the current call to the address returned by `_implementation()`.\\n *\\n * This function does not return to its internall call site, it will return directly to the external caller.\\n */\\n function _fallback() internal virtual {\\n _beforeFallback();\\n _delegate(_implementation());\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\\n * function in the contract matches the call data.\\n */\\n fallback() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\\n * is empty.\\n */\\n receive() external payable virtual {\\n _fallback();\\n }\\n\\n /**\\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\\n * call, or as part of the Solidity `fallback` or `receive` functions.\\n *\\n * If overriden should call `super._beforeFallback()`.\\n */\\n function _beforeFallback() internal virtual {}\\n}\\n\",\"keccak256\":\"0xd5d1fd16e9faff7fcb3a52e02a8d49156f42a38a03f07b5f1810c21c2149a8ab\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\\n */\\ninterface IBeacon {\\n /**\\n * @dev Must return an address that can be used as a delegate call target.\\n *\\n * {BeaconProxy} will check that this address is a contract.\\n */\\n function implementation() external view returns (address);\\n}\\n\",\"keccak256\":\"0xd50a3421ac379ccb1be435fa646d66a65c986b4924f0849839f08692f39dde61\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3777e696b62134e6177440dbe6e6601c0c156a443f57167194b67e75527439de\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/openzeppelin/utils/StorageSlot.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Library for reading and writing primitive types to specific storage slots.\\n *\\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\\n * This library helps with reading and writing to such slots without the need for inline assembly.\\n *\\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\\n *\\n * Example usage to set ERC1967 implementation slot:\\n * ```\\n * contract ERC1967 {\\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\\n *\\n * function _getImplementation() internal view returns (address) {\\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\\n * }\\n *\\n * function _setImplementation(address newImplementation) internal {\\n * require(Address.isContract(newImplementation), \\\"ERC1967: new implementation is not a contract\\\");\\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\\n * }\\n * }\\n * ```\\n *\\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\\n */\\nlibrary StorageSlot {\\n struct AddressSlot {\\n address value;\\n }\\n\\n struct BooleanSlot {\\n bool value;\\n }\\n\\n struct Bytes32Slot {\\n bytes32 value;\\n }\\n\\n struct Uint256Slot {\\n uint256 value;\\n }\\n\\n /**\\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\\n */\\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\\n */\\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\\n */\\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n\\n /**\\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\\n */\\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\\n assembly {\\n r.slot := slot\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfe1b7a9aa2a530a9e705b220e26cd584e2fbdc9602a3a1066032b12816b46aca\",\"license\":\"MIT\"},\"hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\\\";\\n\\n/**\\n * @dev This contract implements a proxy that is upgradeable by an admin.\\n *\\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\\n * clashing], which can potentially be used in an attack, this contract uses the\\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\\n * things that go hand in hand:\\n *\\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\\n * that call matches one of the admin functions exposed by the proxy itself.\\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\\n * \\\"admin cannot fallback to proxy target\\\".\\n *\\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\\n * to sudden errors when trying to call a function from the proxy implementation.\\n *\\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\\n */\\ncontract OptimizedTransparentUpgradeableProxy is ERC1967Proxy {\\n address internal immutable _ADMIN;\\n\\n /**\\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\\n */\\n constructor(\\n address _logic,\\n address admin_,\\n bytes memory _data\\n ) payable ERC1967Proxy(_logic, _data) {\\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\\\"eip1967.proxy.admin\\\")) - 1));\\n _ADMIN = admin_;\\n\\n // still store it to work with EIP-1967\\n bytes32 slot = _ADMIN_SLOT;\\n // solhint-disable-next-line no-inline-assembly\\n assembly {\\n sstore(slot, admin_)\\n }\\n emit AdminChanged(address(0), admin_);\\n }\\n\\n /**\\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\\n */\\n modifier ifAdmin() {\\n if (msg.sender == _getAdmin()) {\\n _;\\n } else {\\n _fallback();\\n }\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\\n */\\n function admin() external ifAdmin returns (address admin_) {\\n admin_ = _getAdmin();\\n }\\n\\n /**\\n * @dev Returns the current implementation.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\\n *\\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\\n */\\n function implementation() external ifAdmin returns (address implementation_) {\\n implementation_ = _implementation();\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\\n */\\n function upgradeTo(address newImplementation) external ifAdmin {\\n _upgradeToAndCall(newImplementation, bytes(\\\"\\\"), false);\\n }\\n\\n /**\\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\\n * proxied contract.\\n *\\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\\n */\\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\\n _upgradeToAndCall(newImplementation, data, true);\\n }\\n\\n /**\\n * @dev Returns the current admin.\\n */\\n function _admin() internal view virtual returns (address) {\\n return _getAdmin();\\n }\\n\\n /**\\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\\n */\\n function _beforeFallback() internal virtual override {\\n require(msg.sender != _getAdmin(), \\\"TransparentUpgradeableProxy: admin cannot fallback to proxy target\\\");\\n super._beforeFallback();\\n }\\n\\n function _getAdmin() internal view virtual override returns (address) {\\n return _ADMIN;\\n }\\n}\\n\",\"keccak256\":\"0xa30117644e27fa5b49e162aae2f62b36c1aca02f801b8c594d46e2024963a534\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60a0604052604051610c8f380380610c8f8339810160408190526100229161039f565b828161004f60017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd61046a565b5f80516020610c488339815191521461006a5761006a610489565b61007582825f610123565b506100a3905060017fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610461046a565b5f80516020610c28833981519152146100be576100be610489565b6001600160a01b03821660808190525f80516020610c28833981519152838155604080515f8152602081019390935290917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050506104e8565b61012c8361014e565b5f825111806101385750805b1561014957610147838361018d565b505b505050565b610157816101bb565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606101b28383604051806060016040528060278152602001610c686027913961025b565b90505b92915050565b6001600160a01b0381163b61022d5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b5f80516020610c4883398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6102c35760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610224565b5f80856001600160a01b0316856040516102dd919061049d565b5f60405180830381855af49150503d805f8114610315576040519150601f19603f3d011682016040523d82523d5f602084013e61031a565b606091505b50909250905061032b828286610337565b925050505b9392505050565b60608315610346575081610330565b8251156103565782518084602001fd5b8160405162461bcd60e51b815260040161022491906104b3565b80516001600160a01b0381168114610386575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f805f606084860312156103b1575f80fd5b6103ba84610370565b92506103c860208501610370565b60408501519092506001600160401b03808211156103e4575f80fd5b818601915086601f8301126103f7575f80fd5b8151818111156104095761040961038b565b604051601f8201601f19908116603f011681019083821181831017156104315761043161038b565b81604052828152896020848701011115610449575f80fd5b8260208601602083015e5f6020848301015280955050505050509250925092565b818103818111156101b557634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52600160045260245ffd5b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b6080516107066105225f395f818160eb0152818161013f015281816101bf0152818161020801528181610239015261025d01526107065ff3fe608060405260043610610042575f3560e01c80633659cfe6146100595780634f1ef286146100785780635c60da1b1461008b578063f851a440146100bb57610051565b366100515761004f6100cf565b005b61004f6100cf565b348015610064575f80fd5b5061004f6100733660046105c9565b6100e9565b61004f6100863660046105e2565b61013d565b348015610096575f80fd5b5061009f6101bc565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c6575f80fd5b5061009f610205565b6100d761025b565b6100e76100e2610309565b61033b565b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610135576101328160405180602001604052805f8152505f610359565b50565b6101326100cf565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101b4576101af8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610359915050565b505050565b6101af6100cf565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa576101f5610309565b905090565b6102026100cf565b90565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa57507f000000000000000000000000000000000000000000000000000000000000000090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036100e75760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101f57f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b365f80375f80365f845af43d5f803e808015610355573d5ff35b3d5ffd5b61036283610383565b5f8251118061036e5750805b156101af5761037d83836103c2565b50505050565b61038c816103ee565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103e783836040518060600160405280602781526020016106aa6027913961049c565b9392505050565b6001600160a01b0381163b61045b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610300565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6105045760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610300565b5f80856001600160a01b03168560405161051e919061065e565b5f60405180830381855af49150503d805f8114610556576040519150601f19603f3d011682016040523d82523d5f602084013e61055b565b606091505b509150915061056b828286610575565b9695505050505050565b606083156105845750816103e7565b8251156105945782518084602001fd5b8160405162461bcd60e51b81526004016103009190610674565b80356001600160a01b03811681146105c4575f80fd5b919050565b5f602082840312156105d9575f80fd5b6103e7826105ae565b5f805f604084860312156105f4575f80fd5b6105fd846105ae565b9250602084013567ffffffffffffffff80821115610619575f80fd5b818601915086601f83011261062c575f80fd5b81358181111561063a575f80fd5b87602082850101111561064b575f80fd5b6020830194508093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d447ec2a4b47962d9e959ca0ec6571d77747999259501e773b66fdcaef0e50bf64736f6c63430008190033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", + "deployedBytecode": "0x608060405260043610610042575f3560e01c80633659cfe6146100595780634f1ef286146100785780635c60da1b1461008b578063f851a440146100bb57610051565b366100515761004f6100cf565b005b61004f6100cf565b348015610064575f80fd5b5061004f6100733660046105c9565b6100e9565b61004f6100863660046105e2565b61013d565b348015610096575f80fd5b5061009f6101bc565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c6575f80fd5b5061009f610205565b6100d761025b565b6100e76100e2610309565b61033b565b565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163303610135576101328160405180602001604052805f8152505f610359565b50565b6101326100cf565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101b4576101af8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060019250610359915050565b505050565b6101af6100cf565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa576101f5610309565b905090565b6102026100cf565b90565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036101fa57507f000000000000000000000000000000000000000000000000000000000000000090565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633036100e75760405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b5f6101f57f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b365f80375f80365f845af43d5f803e808015610355573d5ff35b3d5ffd5b61036283610383565b5f8251118061036e5750805b156101af5761037d83836103c2565b50505050565b61038c816103ee565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606103e783836040518060600160405280602781526020016106aa6027913961049c565b9392505050565b6001600160a01b0381163b61045b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b6064820152608401610300565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60606001600160a01b0384163b6105045760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b6064820152608401610300565b5f80856001600160a01b03168560405161051e919061065e565b5f60405180830381855af49150503d805f8114610556576040519150601f19603f3d011682016040523d82523d5f602084013e61055b565b606091505b509150915061056b828286610575565b9695505050505050565b606083156105845750816103e7565b8251156105945782518084602001fd5b8160405162461bcd60e51b81526004016103009190610674565b80356001600160a01b03811681146105c4575f80fd5b919050565b5f602082840312156105d9575f80fd5b6103e7826105ae565b5f805f604084860312156105f4575f80fd5b6105fd846105ae565b9250602084013567ffffffffffffffff80821115610619575f80fd5b818601915086601f83011261062c575f80fd5b81358181111561063a575f80fd5b87602082850101111561064b575f80fd5b6020830194508093505050509250925092565b5f82518060208501845e5f920191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f8301168401019150509291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d447ec2a4b47962d9e959ca0ec6571d77747999259501e773b66fdcaef0e50bf64736f6c63430008190033", + "devdoc": { + "details": "This contract implements a proxy that is upgradeable by an admin. 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.", + "events": { + "AdminChanged(address,address)": { + "details": "Emitted when the admin account has changed." + }, + "BeaconUpgraded(address)": { + "details": "Emitted when the beacon is upgraded." + }, + "Upgraded(address)": { + "details": "Emitted when the implementation is upgraded." + } + }, + "kind": "dev", + "methods": { + "admin()": { + "details": "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`" + }, + "constructor": { + "details": "Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}." + }, + "implementation()": { + "details": "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`" + }, + "upgradeTo(address)": { + "details": "Upgrade the implementation of the proxy. NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}." + }, + "upgradeToAndCall(address,bytes)": { + "details": "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}." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": null + } +} diff --git a/deployments/bsctestnet/solcInputs/4e92f74ff4913274028244b68ad09cb7.json b/deployments/bsctestnet/solcInputs/4e92f74ff4913274028244b68ad09cb7.json new file mode 100644 index 00000000..84766072 --- /dev/null +++ b/deployments/bsctestnet/solcInputs/4e92f74ff4913274028244b68ad09cb7.json @@ -0,0 +1,88 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./OwnableUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n function __Ownable2Step_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable2Step_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() public virtual {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n function __Ownable_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable_init_unchained() internal onlyInitializing {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../../utils/AddressUpgradeable.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n * @custom:oz-retyped-from bool\n */\n uint8 private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint8 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts.\n *\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\n * constructor.\n *\n * Emits an {Initialized} event.\n */\n modifier initializer() {\n bool isTopLevelCall = !_initializing;\n require(\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\n \"Initializable: contract is already initialized\"\n );\n _initialized = 1;\n if (isTopLevelCall) {\n _initializing = true;\n }\n _;\n if (isTopLevelCall) {\n _initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\n * are added through upgrades and that require initialization.\n *\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\n * cannot be nested. If one is invoked in the context of another, execution will revert.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n *\n * WARNING: setting the version to 255 will prevent any future reinitialization.\n *\n * Emits an {Initialized} event.\n */\n modifier reinitializer(uint8 version) {\n require(!_initializing && _initialized < version, \"Initializable: contract is already initialized\");\n _initialized = version;\n _initializing = true;\n _;\n _initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n *\n * Emits an {Initialized} event the first time it is successfully executed.\n */\n function _disableInitializers() internal virtual {\n require(!_initializing, \"Initializable: contract is initializing\");\n if (_initialized != type(uint8).max) {\n _initialized = type(uint8).max;\n emit Initialized(type(uint8).max);\n }\n }\n\n /**\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\n */\n function _getInitializedVersion() internal view returns (uint8) {\n return _initialized;\n }\n\n /**\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\n */\n function _isInitializing() internal view returns (bool) {\n return _initializing;\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary AddressUpgradeable {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n *\n * Furthermore, `isContract` will also return true if the target contract within\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\n * which only has an effect at the end of a transaction.\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\n\nimport \"./IAccessControlManagerV8.sol\";\n\n/**\n * @title AccessControlledV8\n * @author Venus\n * @notice This contract is helper between access control manager and actual contract. This contract further inherited by other contract (using solidity 0.8.13)\n * to integrate access controlled mechanism. It provides initialise methods and verifying access methods.\n */\nabstract contract AccessControlledV8 is Initializable, Ownable2StepUpgradeable {\n /// @notice Access control manager contract\n IAccessControlManagerV8 internal _accessControlManager;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n\n /// @notice Emitted when access control manager contract address is changed\n event NewAccessControlManager(address oldAccessControlManager, address newAccessControlManager);\n\n /// @notice Thrown when the action is prohibited by AccessControlManager\n error Unauthorized(address sender, address calledContract, string methodSignature);\n\n function __AccessControlled_init(address accessControlManager_) internal onlyInitializing {\n __Ownable2Step_init();\n __AccessControlled_init_unchained(accessControlManager_);\n }\n\n function __AccessControlled_init_unchained(address accessControlManager_) internal onlyInitializing {\n _setAccessControlManager(accessControlManager_);\n }\n\n /**\n * @notice Sets the address of AccessControlManager\n * @dev Admin function to set address of AccessControlManager\n * @param accessControlManager_ The new address of the AccessControlManager\n * @custom:event Emits NewAccessControlManager event\n * @custom:access Only Governance\n */\n function setAccessControlManager(address accessControlManager_) external onlyOwner {\n _setAccessControlManager(accessControlManager_);\n }\n\n /**\n * @notice Returns the address of the access control manager contract\n */\n function accessControlManager() external view returns (IAccessControlManagerV8) {\n return _accessControlManager;\n }\n\n /**\n * @dev Internal function to set address of AccessControlManager\n * @param accessControlManager_ The new address of the AccessControlManager\n */\n function _setAccessControlManager(address accessControlManager_) internal {\n require(address(accessControlManager_) != address(0), \"invalid acess control manager address\");\n address oldAccessControlManager = address(_accessControlManager);\n _accessControlManager = IAccessControlManagerV8(accessControlManager_);\n emit NewAccessControlManager(oldAccessControlManager, accessControlManager_);\n }\n\n /**\n * @notice Reverts if the call is not allowed by AccessControlManager\n * @param signature Method signature\n */\n function _checkAccessAllowed(string memory signature) internal view {\n bool isAllowedToCall = _accessControlManager.isAllowedToCall(msg.sender, signature);\n\n if (!isAllowedToCall) {\n revert Unauthorized(msg.sender, address(this), signature);\n }\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/access/IAccessControl.sol\";\n\n/**\n * @title IAccessControlManagerV8\n * @author Venus\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\n */\ninterface IAccessControlManagerV8 is IAccessControl {\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\n\n function revokeCallPermission(\n address contractAddress,\n string calldata functionSig,\n address accountToRevoke\n ) external;\n\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\n\n function hasPermission(\n address account,\n address contractAddress,\n string calldata functionSig\n ) external view returns (bool);\n}\n" + }, + "@venusprotocol/solidity-utilities/contracts/constants.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\nuint256 constant EXP_SCALE = 1e18;\n\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\nuint256 constant MANTISSA_ONE = EXP_SCALE;\n\n/// @dev The approximate number of seconds per year\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\n" + }, + "@venusprotocol/solidity-utilities/contracts/validators.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\nerror ZeroAddressNotAllowed();\n\n/// @notice Thrown if the supplied value is 0 where it is not allowed\nerror ZeroValueNotAllowed();\n\n/// @notice Checks if the provided address is nonzero, reverts otherwise\n/// @param address_ Address to check\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\nfunction ensureNonzeroAddress(address address_) pure {\n if (address_ == address(0)) {\n revert ZeroAddressNotAllowed();\n }\n}\n\n/// @notice Checks if the provided value is nonzero, reverts otherwise\n/// @param value_ Value to check\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\nfunction ensureNonzeroValue(uint256 value_) pure {\n if (value_ == 0) {\n revert ZeroValueNotAllowed();\n }\n}\n" + }, + "contracts/DeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { VBep20Interface } from \"./interfaces/VBep20Interface.sol\";\nimport { ResilientOracleInterface } from \"./interfaces/OracleInterface.sol\";\nimport { IDeviationBoundedOracle } from \"./interfaces/IDeviationBoundedOracle.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { Transient } from \"./lib/Transient.sol\";\n\n/**\n * @title DeviationBoundedOracle\n * @author Venus\n * @notice The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations.\n *\n * It maintains a per-market rolling min/max price window. When the current spot price deviates\n * significantly from the window bounds, protection mode activates automatically and conservative\n * pricing kicks in:\n * - Collateral is valued at min(spot, windowMin) — caps collateral value at recent window low\n * - Debt is valued at max(spot, windowMax) — floors debt value at recent window high\n *\n * This protects against instantaneous or short-duration price manipulation attacks on low-liquidity\n * collateral tokens. Sustained attacks beyond the window period are expected to be handled by\n * off-chain monitoring systems.\n *\n * The oracle exposes both view and non-view price functions. The non-view variants update the\n * price window and trigger protection. The view variants read stored state only. A transient\n * price cache avoids redundant ResilientOracle calls within the same transaction when\n * updateProtectionState is called before the view price reads.\n */\ncontract DeviationBoundedOracle is AccessControlledV8, IDeviationBoundedOracle {\n /// @notice Minimum allowed threshold value (5%) to account for keeper deadband\n uint256 public constant MIN_THRESHOLD = 5e16;\n\n /// @notice Maximum allowed threshold value (50%)\n uint256 public constant MAX_THRESHOLD = 50e16;\n\n /// @notice Keeper deadband threshold (5%) — min/max corrections below this are suppressed\n uint256 public constant KEEPER_DEADBAND = 5e16;\n\n /// @notice Resilient Oracle used to fetch spot prices\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Native market address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable nativeMarket;\n\n /// @notice VAI address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vai;\n\n /// @notice Transient storage slot for caching final collateral prices within a transaction\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/cache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/cache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant COLLATERAL_PRICE_CACHE_SLOT =\n 0x818cfa9b1e1b1cc716656acdb79a94121ed79bfb196bf958683ed2a3277cb200;\n\n /// @notice Transient storage slot for caching final debt prices within a transaction\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant DEBT_PRICE_CACHE_SLOT = 0x84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb8970600;\n\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\n /// and can serve as any underlying asset of a market that supports native tokens\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Per-asset protection state\n mapping(address => MarketProtectionState) public assetProtectionConfig;\n\n /// @notice Append-only array of all assets ever initialized, used for enumeration\n address[] public allAssets;\n\n /// @notice Storage gap for upgrades\n uint256[48] private __gap;\n\n /**\n * @notice Constructor for the implementation contract. Sets immutable variables.\n * @param _resilientOracle Address of the ResilientOracle contract\n * @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\n * @param vaiAddress The address of the VAI token, or address(0) if VAI is not deployed on the chain.\n * @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(ResilientOracleInterface _resilientOracle, address nativeMarketAddress, address vaiAddress) {\n ensureNonzeroAddress(address(_resilientOracle));\n ensureNonzeroAddress(nativeMarketAddress);\n RESILIENT_ORACLE = _resilientOracle;\n nativeMarket = nativeMarketAddress;\n vai = vaiAddress;\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n // ----- Non-view price functions (update window + trigger protection) -----\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns the conservative (lower) price when protection is active.\n * Used by keepers or direct callers who want atomic update + read.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice) {\n (collateralPrice, ) = _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns the conservative (higher) price when protection is active.\n * Used by keepers or direct callers who want atomic update + read.\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice) {\n (, debtPrice) = _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns both conservative prices in a single call.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice) {\n return _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Fetches the spot price, updates the protection window, and caches the resolved\n * bounded prices in transient storage for the duration of the transaction.\n * @dev Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before\n * liquidity calculations). Subsequent calls to getBoundedCollateralPriceView /\n * getBoundedDebtPriceView within the same transaction will read from the transient cache\n * instead of querying ResilientOracle again, keeping those functions as `view` and\n * avoiding redundant oracle calls.\n * @param vToken vToken address\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external {\n _updateAndGetBoundedPrices(vToken);\n }\n\n // ----- View price functions (read stored/cached state only) -----\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first (populated by a prior updateProtectionState call\n * in the same transaction). Falls back to ResilientOracle on cache miss.\n * Returns min(spot, windowMin) when protection is active, spot otherwise.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 collateralPrice) {\n (collateralPrice, ) = _computeBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first (populated by a prior updateProtectionState call\n * in the same transaction). Falls back to ResilientOracle on cache miss.\n * Returns max(spot, windowMax) when protection is active, spot otherwise.\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 debtPrice) {\n (, debtPrice) = _computeBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first; falls back to ResilientOracle on cache miss.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice) {\n return _computeBoundedPrices(vToken);\n }\n\n // ----- Keeper functions -----\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @dev Called by the keeper to push corrected min values from the off-chain sliding window.\n * Constraint: newMin must be at or below the current spot price.\n * @param asset The underlying asset address\n * @param newMin The new minimum price\n * @custom:access Only authorized keeper addresses\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external {\n _checkAccessAllowed(\"updateMinPrice(address,uint128)\");\n _validateAndUpdateBound(asset, newMin, PriceBoundType.MIN);\n }\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @dev Called by the keeper to push corrected max values from the off-chain sliding window.\n * Constraint: newMax must be at or above the current spot price.\n * @param asset The underlying asset address\n * @param newMax The new maximum price\n * @custom:access Only authorized keeper addresses\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external {\n _checkAccessAllowed(\"updateMaxPrice(address,uint128)\");\n _validateAndUpdateBound(asset, newMax, PriceBoundType.MAX);\n }\n\n /**\n * @notice Exits protection mode for a given asset\n * @dev Called by the keeper/monitor after confirming price has normalised.\n * Enforces two conditions on-chain:\n * 1. Cooldown period has elapsed since the last trigger\n * 2. Price range has converged below the exit threshold\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\n * @custom:error PriceRangeNotConverged if window range is still above exit threshold\n * @custom:event ProtectionModeExited\n */\n function exitProtectionMode(address asset) external {\n _checkAccessAllowed(\"exitProtectionMode(address)\");\n ensureNonzeroAddress(asset);\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (!state.currentlyUsingProtectedPrice) revert ProtectedPriceInactive(asset);\n\n if (block.timestamp < uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod)) {\n revert CooldownNotElapsed(asset, state.lastProtectionTriggeredAt, state.cooldownPeriod);\n }\n\n // exit protected price if price range has converged below exit threshold\n uint256 rangeRatio = _computePriceBoundRatio(state.minPrice, state.maxPrice);\n if (rangeRatio >= state.resetThreshold) {\n revert PriceRangeNotConverged(asset, rangeRatio, state.resetThreshold);\n }\n\n state.currentlyUsingProtectedPrice = false;\n state.lastProtectionTriggeredAt = 0;\n emit ProtectionModeExited(asset);\n }\n\n // ----- Admin functions (governance-gated) -----\n\n /**\n * @notice Initializes protection for a new asset\n * @param asset The underlying asset address\n * @param cooldownPeriod Minimum time protection stays active after last trigger\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\n * @param enableBoundedPricing Whether to enable bounded pricing immediately upon initialization\n * @custom:access Only Governance\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(\n address asset,\n uint64 cooldownPeriod,\n uint256 triggerThreshold,\n uint256 resetThreshold,\n bool enableBoundedPricing\n ) external {\n _checkAccessAllowed(\"setTokenConfig(address,uint64,uint256,uint256,bool)\");\n _setTokenConfig(asset, cooldownPeriod, triggerThreshold, resetThreshold, enableBoundedPricing);\n }\n\n /**\n * @notice Batch-initializes protection for multiple assets in a single transaction\n * @param assets Array of underlying asset addresses\n * @param cooldownPeriods Array of cooldown periods (seconds)\n * @param triggerThresholds Array of trigger thresholds (mantissa)\n * @param resetThresholds Array of reset thresholds (mantissa)\n * @param enableBoundedPricings Array of whether to enable bounded pricing per asset\n * @custom:access Only Governance\n * @custom:error InvalidArrayLength if array lengths do not match\n * @custom:event ProtectionInitialized for each asset\n * @custom:event BoundedPricingWhitelistUpdated for each asset\n */\n function setTokenConfigs(\n address[] calldata assets,\n uint64[] calldata cooldownPeriods,\n uint256[] calldata triggerThresholds,\n uint256[] calldata resetThresholds,\n bool[] calldata enableBoundedPricings\n ) external {\n _checkAccessAllowed(\"setTokenConfigs(address[],uint64[],uint256[],uint256[],bool[])\");\n uint256 len = assets.length;\n if (\n len == 0 ||\n len != cooldownPeriods.length ||\n len != triggerThresholds.length ||\n len != resetThresholds.length ||\n len != enableBoundedPricings.length\n ) revert InvalidArrayLength();\n\n for (uint256 i; i < len; ++i) {\n _setTokenConfig(\n assets[i],\n cooldownPeriods[i],\n triggerThresholds[i],\n resetThresholds[i],\n enableBoundedPricings[i]\n );\n }\n }\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds\n * @custom:access Only Governance\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external {\n _checkAccessAllowed(\"setCooldownPeriod(address,uint64)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(newCooldown);\n\n MarketProtectionState storage state = _ensureInitialized(asset);\n emit CooldownPeriodSet(asset, state.cooldownPeriod, newCooldown);\n state.cooldownPeriod = newCooldown;\n }\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external {\n _checkAccessAllowed(\"setThresholds(address,uint256,uint256)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(newTriggerThreshold);\n ensureNonzeroValue(newResetThreshold);\n if (newTriggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(newTriggerThreshold, MIN_THRESHOLD);\n if (newTriggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(newTriggerThreshold, MAX_THRESHOLD);\n if (newResetThreshold >= newTriggerThreshold) revert InvalidResetThreshold(newResetThreshold);\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (newTriggerThreshold != state.triggerThreshold) {\n emit TriggerThresholdSet(asset, state.triggerThreshold, newTriggerThreshold);\n state.triggerThreshold = uint128(newTriggerThreshold);\n }\n if (newResetThreshold != state.resetThreshold) {\n emit ResetThresholdSet(asset, state.resetThreshold, newResetThreshold);\n state.resetThreshold = uint128(newResetThreshold);\n }\n }\n\n /**\n * @notice Sets whether an asset is enabled for bounded pricing\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external {\n _checkAccessAllowed(\"setAssetBoundedPricingEnabled(address,bool)\");\n ensureNonzeroAddress(asset);\n\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (!enabled && state.currentlyUsingProtectedPrice) {\n revert ProtectedPriceActive(asset);\n }\n\n if (state.isBoundedPricingEnabled == enabled) return;\n\n // reset the window if re-enabling\n if (enabled) {\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\n _setMinPrice(state, asset, spotU128);\n _setMaxPrice(state, asset, spotU128);\n }\n\n state.isBoundedPricingEnabled = enabled;\n emit BoundedPricingWhitelistUpdated(asset, enabled);\n }\n\n // ----- View helpers -----\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory) {\n return allAssets;\n }\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool) {\n return assetProtectionConfig[asset].isBoundedPricingEnabled;\n }\n\n /**\n * @notice Checks if the asset is currently using the protected (bounded) price\n * @param asset The underlying asset address\n * @return True if the asset is currently using the protected price instead of spot\n */\n function currentlyUsingProtectedPrice(address asset) external view returns (bool) {\n return assetProtectionConfig[asset].currentlyUsingProtectedPrice;\n }\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @dev Iterates the append-only allAssets array and filters by isBoundedPricingEnabled.\n * Gas-free for off-chain callers.\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory) {\n uint256 len = allAssets.length;\n address[] memory temp = new address[](len);\n uint256 count;\n for (uint256 i; i < len; ++i) {\n if (assetProtectionConfig[allAssets[i]].isBoundedPricingEnabled) {\n temp[count++] = allAssets[i];\n }\n }\n address[] memory result = new address[](count);\n for (uint256 i; i < count; ++i) {\n result[i] = temp[i];\n }\n return result;\n }\n\n /**\n * @notice Checks if protection can be exited for an asset\n * @dev Returns true when both conditions are met:\n * 1. Cooldown period has elapsed since last trigger\n * 2. Price range has converged below exit threshold\n * @param asset The underlying asset address\n * @return True if protection can be disabled\n */\n function canExitProtection(address asset) external view returns (bool) {\n MarketProtectionState storage state = assetProtectionConfig[asset];\n return\n state.currentlyUsingProtectedPrice &&\n block.timestamp >= uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod) &&\n _computePriceBoundRatio(state.minPrice, state.maxPrice) < state.resetThreshold;\n }\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the deadband\n * from the keeper's proposed window values\n * @dev Allows the keeper to identify stale windows in a single call, avoiding N individual reads.\n * Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's off-chain window minimum prices\n * @param proposedMaxs Keeper's off-chain window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate) {\n uint256 len = assets.length;\n if (len != proposedMins.length || len != proposedMaxs.length) revert InvalidArrayLength();\n\n needsMinUpdate = new bool[](len);\n needsMaxUpdate = new bool[](len);\n\n for (uint256 i; i < len; ++i) {\n MarketProtectionState storage state = assetProtectionConfig[assets[i]];\n needsMinUpdate[i] = _exceedsCorrectionDeadband(state.minPrice, proposedMins[i]);\n needsMaxUpdate[i] = _exceedsCorrectionDeadband(state.maxPrice, proposedMaxs[i]);\n }\n }\n\n // ----- Internal functions -----\n\n /**\n * @notice Initializes protection parameters and price window for a single asset\n * @dev Fetches the current spot price from ResilientOracle to seed the initial min/max window,\n * confirming the oracle is live for this asset before it is listed. Both bounds start at\n * spot so the window expands naturally as prices move. Can only be called once per asset.\n * @param asset The underlying asset address\n * @param cooldownPeriod Minimum time protection stays active after last trigger\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\n * @param enableBoundedPricing Whether to enable bounded pricing immediately upon initialization\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n */\n function _setTokenConfig(\n address asset,\n uint64 cooldownPeriod,\n uint256 triggerThreshold,\n uint256 resetThreshold,\n bool enableBoundedPricing\n ) internal {\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(cooldownPeriod);\n ensureNonzeroValue(triggerThreshold);\n ensureNonzeroValue(resetThreshold);\n if (assetProtectionConfig[asset].asset != address(0)) revert MarketAlreadyInitialized(asset);\n if (triggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(triggerThreshold, MIN_THRESHOLD);\n if (triggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(triggerThreshold, MAX_THRESHOLD);\n if (resetThreshold >= triggerThreshold) revert InvalidResetThreshold(resetThreshold);\n if (asset == vai) revert VAINotAllowed();\n\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\n\n assetProtectionConfig[asset] = MarketProtectionState({\n minPrice: spotU128,\n maxPrice: spotU128,\n currentlyUsingProtectedPrice: false,\n isBoundedPricingEnabled: enableBoundedPricing,\n lastProtectionTriggeredAt: 0,\n cooldownPeriod: cooldownPeriod,\n asset: asset,\n triggerThreshold: uint128(triggerThreshold),\n resetThreshold: uint128(resetThreshold)\n });\n\n allAssets.push(asset);\n\n emit ProtectionInitialized(asset, spotU128, spotU128, cooldownPeriod, triggerThreshold);\n emit BoundedPricingWhitelistUpdated(asset, enableBoundedPricing);\n }\n\n /**\n * @notice Validates and applies a keeper-provided min or max price update\n * @param asset The underlying asset address\n * @param newPrice The new price value to set\n * @param boundType Whether this is a MIN or MAX bound update\n * @custom:error ZeroPriceNotAllowed if newPrice is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if boundType is MIN and newPrice exceeds the current spot or is at or above maxPrice\n * @custom:error InvalidMaxPrice if boundType is MAX and newPrice is below the current spot or is at or below minPrice\n */\n function _validateAndUpdateBound(address asset, uint128 newPrice, PriceBoundType boundType) internal {\n ensureNonzeroAddress(asset);\n if (newPrice == 0) revert ZeroPriceNotAllowed();\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n uint256 currentSpot = _fetchSpotPrice(asset);\n if (boundType == PriceBoundType.MIN) {\n if (newPrice >= state.maxPrice || uint256(newPrice) > currentSpot)\n revert InvalidMinPrice(asset, newPrice, currentSpot);\n _setMinPrice(state, asset, newPrice);\n } else if (boundType == PriceBoundType.MAX) {\n if (newPrice <= state.minPrice || uint256(newPrice) < currentSpot)\n revert InvalidMaxPrice(asset, newPrice, currentSpot);\n _setMaxPrice(state, asset, newPrice);\n }\n }\n\n /**\n * @notice Shared non-view logic for all bounded price functions.\n * Fetches spot, updates window, triggers protection if needed, and returns both bounded prices.\n * @param vToken vToken address\n * @return minPrice The bounded lower (collateral) price\n * @return maxPrice The bounded upper (debt) price\n */\n function _updateAndGetBoundedPrices(address vToken) internal returns (uint256 minPrice, uint256 maxPrice) {\n address asset = _getUnderlyingAsset(vToken);\n\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\n (minPrice, maxPrice) = _getCachedPrices(asset);\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\n\n // return early if failure from resilient oracle to prevent cold SLOAD\n uint256 spot = _fetchSpotPrice(asset);\n MarketProtectionState storage state = assetProtectionConfig[asset];\n if (!state.isBoundedPricingEnabled) {\n _setCachedPrices(asset, spot, spot);\n return (spot, spot);\n }\n (uint128 updatedMin, uint128 updatedMax, bool windowExpanded) = _expandPriceWindow(state, spot, asset);\n bool protectionActive = _checkAndTriggerProtection(state, spot, asset, windowExpanded);\n (minPrice, maxPrice) = _resolveBoundedPrices(protectionActive, spot, uint256(updatedMin), uint256(updatedMax));\n _setCachedPrices(asset, minPrice, maxPrice);\n }\n\n /**\n * @dev Expands the price window toward extremes if the spot price is a new min or max\n * @param state The market protection state\n * @param spot The current spot price\n * @param asset The underlying asset address (for event emission)\n */\n function _expandPriceWindow(\n MarketProtectionState storage state,\n uint256 spot,\n address asset\n ) internal returns (uint128, uint128, bool) {\n uint128 spotU128 = _safeToUint128(spot);\n uint128 currentMin = state.minPrice;\n uint128 currentMax = state.maxPrice;\n bool windowExpanded;\n if (spotU128 < currentMin) {\n _setMinPrice(state, asset, spotU128);\n currentMin = spotU128;\n windowExpanded = true;\n }\n if (spotU128 > currentMax) {\n _setMaxPrice(state, asset, spotU128);\n currentMax = spotU128;\n windowExpanded = true;\n }\n return (currentMin, currentMax, windowExpanded);\n }\n\n /**\n * @dev Checks if the spot price has deviated beyond the threshold and triggers protection.\n * `lastProtectionTriggeredAt` is reset only on the first trigger or when the price has made a\n * genuine new extreme this update (windowExpanded == true). Recovery within the existing window\n * keeps the cooldown ticking so `exitProtectionMode` remains reachable.\n * @param state The market protection state\n * @param spot The current spot price\n * @param asset The underlying asset address (for event emission)\n * @param windowExpanded True if `_expandPriceWindow` recorded a new low or new high this call\n */\n function _checkAndTriggerProtection(\n MarketProtectionState storage state,\n uint256 spot,\n address asset,\n bool windowExpanded\n ) internal returns (bool triggered) {\n if (_exceedsDeviationThreshold(spot, state.minPrice, state.maxPrice, state.triggerThreshold)) {\n bool enteringProtection = !state.currentlyUsingProtectedPrice;\n if (enteringProtection || windowExpanded) {\n state.lastProtectionTriggeredAt = uint64(block.timestamp);\n }\n if (enteringProtection) {\n state.currentlyUsingProtectedPrice = true;\n }\n emit ProtectionTriggered(asset, spot, state.minPrice, state.maxPrice);\n return true;\n }\n if (state.currentlyUsingProtectedPrice) return true;\n }\n\n /**\n * @notice Resolves the final bounded collateral and debt prices given a spot, window bounds, and protection flag.\n * @dev When protection is active: collateral = min(spot, windowMin), debt = max(spot, windowMax).\n * When protection is inactive: both return spot.\n * @param protectionActive Whether the market protection window is currently active\n * @param spot The current spot price\n * @param windowMin The lower bound of the price window\n * @param windowMax The upper bound of the price window\n * @return minPrice The resolved lower-bound (collateral) price\n * @return maxPrice The resolved upper-bound (debt) price\n */\n function _resolveBoundedPrices(\n bool protectionActive,\n uint256 spot,\n uint256 windowMin,\n uint256 windowMax\n ) internal pure returns (uint256, uint256) {\n if (!protectionActive) return (spot, spot);\n return (spot < windowMin ? spot : windowMin, spot > windowMax ? spot : windowMax);\n }\n\n /**\n * @notice Shared view logic for all bounded price view functions.\n * Checks transient cache first for an early return; on miss, fetches from oracle\n * and computes both prices without state mutations.\n * @param vToken vToken address\n * @return minPrice The bounded lower (collateral) price\n * @return maxPrice The bounded upper (debt) price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function _computeBoundedPrices(address vToken) internal view returns (uint256 minPrice, uint256 maxPrice) {\n address asset = _getUnderlyingAsset(vToken);\n\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\n (minPrice, maxPrice) = _getCachedPrices(asset);\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\n\n // Cache miss — fetch from oracle and compute without state mutations\n uint256 spot = _fetchSpotPrice(asset);\n MarketProtectionState storage state = assetProtectionConfig[asset];\n if (!state.isBoundedPricingEnabled) return (spot, spot);\n\n // Mirror _expandPriceWindow logic: compute what the window would be after expansion\n uint128 spotU128 = _safeToUint128(spot);\n uint128 windowMin128 = spot < uint256(state.minPrice) ? spotU128 : state.minPrice;\n uint128 windowMax128 = spot > uint256(state.maxPrice) ? spotU128 : state.maxPrice;\n\n bool shouldProtect = state.currentlyUsingProtectedPrice ||\n _exceedsDeviationThreshold(spot, windowMin128, windowMax128, state.triggerThreshold);\n\n (minPrice, maxPrice) = _resolveBoundedPrices(shouldProtect, spot, uint256(windowMin128), uint256(windowMax128));\n }\n\n /**\n * @dev Computes the relative spread between the price window bounds as a ratio scaled by EXP_SCALE.\n * Formula: \\((maxPrice - minPrice) / minPrice\\), scaled by `EXP_SCALE`.\n * Used to measure how much the window has converged -- compared against `resetThreshold`\n * to determine whether the price window is tight enough to exit protection mode.\n * @param minPrice The minimum price in the window\n * @param maxPrice The maximum price in the window\n * @return The scaled bound ratio \\(((max - min) * EXP_SCALE) / min\\)\n */\n function _computePriceBoundRatio(uint128 minPrice, uint128 maxPrice) internal pure returns (uint256) {\n uint256 range = uint256(maxPrice) - uint256(minPrice);\n return (range * EXP_SCALE) / uint256(minPrice);\n }\n\n /**\n * @notice Checks if the spot price has deviated beyond the threshold from the window bounds\n * @dev Pump detection: spot > minPrice * (1 + threshold)\n * Crash detection: spot < maxPrice * (1 - threshold)\n * @param spot The current spot price\n * @param minPrice The minimum price in the window\n * @param maxPrice The maximum price in the window\n * @param threshold The deviation threshold (mantissa)\n * @return True if deviation is triggered\n */\n function _exceedsDeviationThreshold(\n uint256 spot,\n uint128 minPrice,\n uint128 maxPrice,\n uint256 threshold\n ) internal pure returns (bool) {\n uint256 upperBound = (uint256(minPrice) * (EXP_SCALE + threshold)) / EXP_SCALE;\n uint256 lowerBound = (uint256(maxPrice) * (EXP_SCALE - threshold)) / EXP_SCALE;\n return (spot > upperBound || spot < lowerBound);\n }\n\n /**\n * @dev Returns true if the relative drift between onChain and proposed exceeds KEEPER_DEADBAND\n * @param currentPrice The current on-chain price\n * @param proposedPrice The keeper's proposed price\n * @return True if drift exceeds deadband\n */\n function _exceedsCorrectionDeadband(uint128 currentPrice, uint128 proposedPrice) internal pure returns (bool) {\n if (currentPrice == 0 || proposedPrice == 0) return false;\n uint256 diff = currentPrice > proposedPrice\n ? uint256(currentPrice - proposedPrice)\n : uint256(proposedPrice - currentPrice);\n return (diff * EXP_SCALE) / uint256(currentPrice) > KEEPER_DEADBAND;\n }\n\n /**\n * @dev Sets the minimum price in the window and emits MinPriceUpdated\n * @param state The market protection state\n * @param asset The underlying asset address (for event emission)\n * @param newMin The new minimum price\n */\n function _setMinPrice(MarketProtectionState storage state, address asset, uint128 newMin) internal {\n emit MinPriceUpdated(asset, state.minPrice, newMin);\n state.minPrice = newMin;\n }\n\n /**\n * @dev Sets the maximum price in the window and emits MaxPriceUpdated\n * @param state The market protection state\n * @param asset The underlying asset address (for event emission)\n * @param newMax The new maximum price\n */\n function _setMaxPrice(MarketProtectionState storage state, address asset, uint128 newMax) internal {\n emit MaxPriceUpdated(asset, state.maxPrice, newMax);\n state.maxPrice = newMax;\n }\n\n /**\n * @dev Writes both lower and upper bounded prices to transient storage\n * @param asset The underlying asset address\n * @param minPrice The resolved lower (collateral) price to cache\n * @param maxPrice The resolved upper (debt) price to cache\n */\n function _setCachedPrices(address asset, uint256 minPrice, uint256 maxPrice) internal {\n Transient.cachePrice(COLLATERAL_PRICE_CACHE_SLOT, asset, minPrice);\n Transient.cachePrice(DEBT_PRICE_CACHE_SLOT, asset, maxPrice);\n }\n\n /**\n * @dev Reads a cached final price from transient storage\n * @param asset The underlying asset address\n * @return minPrice The cached minimum price, or 0 on cache miss\n * @return maxPrice The cached maximum price, or 0 on cache miss\n */\n function _getCachedPrices(address asset) internal view returns (uint256 minPrice, uint256 maxPrice) {\n minPrice = Transient.readCachedPrice(COLLATERAL_PRICE_CACHE_SLOT, asset);\n maxPrice = Transient.readCachedPrice(DEBT_PRICE_CACHE_SLOT, asset);\n }\n\n /**\n * @dev This function returns the underlying asset of a vToken\n * @param vToken vToken address\n * @return asset underlying asset address\n */\n function _getUnderlyingAsset(address vToken) private view returns (address asset) {\n ensureNonzeroAddress(vToken);\n if (vToken == nativeMarket) {\n asset = NATIVE_TOKEN_ADDR;\n } else if (vToken == vai) {\n asset = vai;\n } else {\n asset = VBep20Interface(vToken).underlying();\n }\n }\n\n /**\n * @dev Reverts if the market has not been initialized via setTokenConfig\n * @param asset The underlying asset address\n * @return state The market protection state storage pointer\n */\n function _ensureInitialized(address asset) internal view returns (MarketProtectionState storage state) {\n state = assetProtectionConfig[asset];\n if (state.asset == address(0)) revert MarketNotInitialized(asset);\n }\n\n /**\n * @notice Fetches the current spot price for an asset from the ResilientOracle\n * @param asset The underlying asset address\n * @return The current spot price\n */\n function _fetchSpotPrice(address asset) internal view returns (uint256) {\n return RESILIENT_ORACLE.getPrice(asset);\n }\n\n /**\n * @dev Safely casts a uint256 to uint128, reverting on overflow\n * @param value The value to cast\n * @return The value as uint128\n */\n function _safeToUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) revert PriceExceedsUint128(value);\n return uint128(value);\n }\n}\n" + }, + "contracts/interfaces/IDeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IDeviationBoundedOracle {\n // --- Enums ---\n\n /// @notice Identifies whether a price bound is a minimum or maximum\n enum PriceBoundType {\n MIN,\n MAX\n }\n\n // --- Structs ---\n\n /// @notice Per-asset protection state tracking the min/max price window\n struct MarketProtectionState {\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\n uint128 minPrice;\n /// @notice Highest price observed in the current window\n uint128 maxPrice;\n /// @notice Whether protected price is currently being used\n bool currentlyUsingProtectedPrice;\n /// @notice Whether this market is whitelisted for bounded pricing\n bool isBoundedPricingEnabled;\n /// @notice Timestamp of the last protection trigger — reset on every trigger\n uint64 lastProtectionTriggeredAt;\n /// @notice Minimum time protection stays active after last trigger\n uint64 cooldownPeriod;\n /// @notice The underlying asset address, used to verify initialization\n address asset;\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\n uint128 triggerThreshold;\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\n uint128 resetThreshold;\n }\n\n // --- Events ---\n\n /// @notice Emitted when protection is initialized for an asset\n event ProtectionInitialized(\n address indexed asset,\n uint128 minPrice,\n uint128 maxPrice,\n uint64 cooldownPeriod,\n uint256 triggerThreshold\n );\n\n /// @notice Emitted when protection mode is triggered for an asset\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\n\n /// @notice Emitted when protection mode is disabled for an asset\n event ProtectionModeExited(address indexed asset);\n\n /// @notice Emitted when the keeper updates the minimum price for an asset\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\n\n /// @notice Emitted when the keeper updates the maximum price for an asset\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\n\n /// @notice Emitted when the entry threshold is updated for an asset\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\n\n /// @notice Emitted when the exit threshold is updated for an asset\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\n\n /// @notice Emitted when the cooldown period is updated for an asset\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\n\n /// @notice Emitted when an asset's whitelist status changes\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\n\n // --- Errors ---\n\n /// @notice Thrown when trying to initialize protection for an asset that is not initialized\n error MarketNotInitialized(address asset);\n\n /// @notice Thrown when trying to initialize an already initialized market\n error MarketAlreadyInitialized(address asset);\n\n /// @notice Thrown when trying to disable protection that is not active\n error ProtectedPriceInactive(address asset);\n\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\n\n /// @notice Thrown when trying to disable protection before price range has converged\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\n\n /// @notice Thrown when keeper tries to set minPrice above current spot\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\n\n /// @notice Thrown when keeper tries to set maxPrice below current spot\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\n\n /// @notice Thrown when threshold is set below the minimum allowed value\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\n\n /// @notice Thrown when threshold is set above the maximum allowed value\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\n\n /// @notice Thrown when a price exceeds uint128 max\n error PriceExceedsUint128(uint256 price);\n\n /// @notice Thrown when a zero price is provided where a non-zero price is required\n error ZeroPriceNotAllowed();\n\n /// @notice Thrown when trying to initialize protection for VAI\n error VAINotAllowed();\n\n /// @notice Thrown when trying to update for an asset with active protection\n error ProtectedPriceActive(address asset);\n\n /// @notice Thrown when the lengths of the arrays are not equal\n error InvalidArrayLength();\n\n /// @notice Thrown when the exit threshold is set above the deviation threshold\n error InvalidResetThreshold(uint256 resetThreshold);\n\n // --- Non-view price functions (update window + trigger protection) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- State update (call before view price reads to populate transient cache) ---\n\n /**\n * @notice Updates the protection state for a given vToken, caching the resolved price\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\n * reads in the same transaction are served from transient storage.\n * @param vToken vToken address\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external;\n\n // --- View price functions (read stored/cached state only) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first; falls back to ResilientOracle on cache miss.\n * @param vToken vToken address\n * @return price The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first; falls back to ResilientOracle on cache miss.\n * @param vToken vToken address\n * @return price The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first; falls back to ResilientOracle on cache miss.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- Keeper functions ---\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMin is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external;\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMax is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external;\n\n /**\n * @notice Exits protection mode for a given asset once conditions are met\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\n * @custom:event ProtectionModeExited\n */\n function exitProtectionMode(address asset) external;\n\n // --- Admin functions (governance-gated) ---\n\n /**\n * @notice Initializes protection parameters for a new asset\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\n * confirming the oracle is live for this asset before it is listed.\n * @param asset The underlying asset address\n * @param cooldownPeriod Minimum time protection stays active after the last trigger, in seconds\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\n * @param enableBoundedPricing Whether to enable bounded pricing immediately upon initialization\n * @custom:access Only Governance\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(\n address asset,\n uint64 cooldownPeriod,\n uint256 triggerThreshold,\n uint256 resetThreshold,\n bool enableBoundedPricing\n ) external;\n\n /**\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\n * @param assets Array of underlying asset addresses\n * @param cooldownPeriods Array of cooldown periods (seconds)\n * @param triggerThresholds Array of trigger thresholds (mantissa)\n * @param resetThresholds Array of reset thresholds (mantissa)\n * @param enableBoundedPricings Array of whether to enable bounded pricing per asset\n * @custom:access Only Governance\n * @custom:error InvalidArrayLength if array lengths do not match\n * @custom:event ProtectionInitialized for each asset\n * @custom:event BoundedPricingWhitelistUpdated for each asset\n */\n function setTokenConfigs(\n address[] calldata assets,\n uint64[] calldata cooldownPeriods,\n uint256[] calldata triggerThresholds,\n uint256[] calldata resetThresholds,\n bool[] calldata enableBoundedPricings\n ) external;\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds; must be non-zero\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\n\n /**\n * @notice Sets whether bounded pricing is enabled for an asset\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\n\n // --- View helpers ---\n\n /**\n * @notice Returns the full protection state for an asset\n * @param asset The underlying asset address\n * @return minPrice Lowest price observed in the current window\n * @return maxPrice Highest price observed in the current window\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\n * @return cooldownPeriod Minimum time protection stays active after last trigger\n * @return assetAddr The underlying asset address stored in the struct\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\n */\n function assetProtectionConfig(\n address asset\n )\n external\n view\n returns (\n uint128 minPrice,\n uint128 maxPrice,\n bool currentlyUsingProtectedPrice,\n bool isBoundedPricingEnabled,\n uint64 lastProtectionTriggeredAt,\n uint64 cooldownPeriod,\n address assetAddr,\n uint128 triggerThreshold,\n uint128 resetThreshold\n );\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool);\n\n /**\n * @notice Checks if the asset is currently using the protected (bounded) price\n * @param asset The underlying asset address\n * @return True if the asset is currently using the protected price instead of spot\n */\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\n\n /**\n * @notice Checks if protection can be exited for a given asset\n * @param asset The underlying asset address\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\n */\n function canExitProtection(address asset) external view returns (bool);\n\n /**\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\n * @param index Array index\n * @return The asset address at the given index\n */\n function allAssets(uint256 index) external view returns (address);\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory);\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's proposed window minimum prices\n * @param proposedMaxs Keeper's proposed window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\n}\n" + }, + "contracts/interfaces/OracleInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface OracleInterface {\n function getPrice(address asset) external view returns (uint256);\n}\n\ninterface ResilientOracleInterface is OracleInterface {\n function updatePrice(address vToken) external;\n\n function updateAssetPrice(address asset) external;\n\n function getUnderlyingPrice(address vToken) external view returns (uint256);\n}\n\ninterface BoundValidatorInterface {\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool);\n}\n" + }, + "contracts/interfaces/VBep20Interface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\ninterface VBep20Interface is IERC20Metadata {\n /**\n * @notice Underlying asset for this VToken\n */\n function underlying() external view returns (address);\n}\n" + }, + "contracts/lib/Transient.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nlibrary Transient {\n /**\n * @notice Cache the asset price into transient storage\n * @param key address of the asset\n * @param value asset price\n */\n function cachePrice(bytes32 cacheSlot, address key, uint256 value) internal {\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\n assembly (\"memory-safe\") {\n tstore(slot, value)\n }\n }\n\n /**\n * @notice Read cached price from transient storage\n * @param key address of the asset\n * @return value cached asset price\n */\n function readCachedPrice(bytes32 cacheSlot, address key) internal view returns (uint256 value) {\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\n assembly (\"memory-safe\") {\n value := tload(slot)\n }\n }\n}\n" + }, + "contracts/test/DeviationBoundedOracleCaller.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IDeviationBoundedOracle } from \"../interfaces/IDeviationBoundedOracle.sol\";\n\n/// @notice Test helper that batches DeviationBoundedOracle calls in a single transaction\n/// so transient storage (tstore/tload) cache can be tested.\ncontract DeviationBoundedOracleCaller {\n IDeviationBoundedOracle public immutable oracle;\n\n constructor(address _oracle) {\n oracle = IDeviationBoundedOracle(_oracle);\n }\n\n function updateAndGetCollateralPrice(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedCollateralPriceView(vToken);\n }\n\n function updateAndGetDebtPrice(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedDebtPriceView(vToken);\n }\n\n function updateAndGetBothPrices(address vToken) external returns (uint256 collateral, uint256 debt) {\n oracle.updateProtectionState(vToken);\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n function updateThenNonViewCollateral(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedCollateralPrice(vToken);\n }\n\n function twoConsecutiveNonViewCollateral(address vToken) external returns (uint256 first, uint256 second) {\n first = oracle.getBoundedCollateralPrice(vToken);\n second = oracle.getBoundedCollateralPrice(vToken);\n }\n\n /// @notice Non-view wrapper so smock records oracle calls made by the view functions.\n function getViewPricesWithoutUpdateNonView(address vToken) external returns (uint256 collateral, uint256 debt) {\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n /// @notice Calls updateProtectionState on vTokenA, then getBoundedCollateralPriceView on vTokenB.\n function updateAViewB(address vTokenA, address vTokenB) external returns (uint256) {\n oracle.updateProtectionState(vTokenA);\n return oracle.getBoundedCollateralPriceView(vTokenB);\n }\n\n function getViewPricesWithoutUpdate(address vToken) external view returns (uint256 collateral, uint256 debt) {\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200, + "details": { + "yul": true + } + }, + "evmVersion": "cancun", + "outputSelection": { + "*": { + "*": [ + "storageLayout", + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "evm.gasEstimates" + ], + "": ["ast"] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} diff --git a/deployments/bsctestnet/solcInputs/b08018e2eab5f4ca5e3ba0c1798444d8.json b/deployments/bsctestnet/solcInputs/b08018e2eab5f4ca5e3ba0c1798444d8.json new file mode 100644 index 00000000..0355b9cb --- /dev/null +++ b/deployments/bsctestnet/solcInputs/b08018e2eab5f4ca5e3ba0c1798444d8.json @@ -0,0 +1,349 @@ +{ + "language": "Solidity", + "sources": { + "@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorInterface {\n function latestAnswer() external view returns (int256);\n\n function latestTimestamp() external view returns (uint256);\n\n function latestRound() external view returns (uint256);\n\n function getAnswer(uint256 roundId) external view returns (int256);\n\n function getTimestamp(uint256 roundId) external view returns (uint256);\n\n event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);\n\n event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);\n}\n" + }, + "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./AggregatorInterface.sol\";\nimport \"./AggregatorV3Interface.sol\";\n\ninterface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface {}\n" + }, + "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n function decimals() external view returns (uint8);\n\n function description() external view returns (string memory);\n\n function version() external view returns (uint256);\n\n function getRoundData(uint80 _roundId)\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./OwnableUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n function __Ownable2Step_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable2Step_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() public virtual {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n function __Ownable_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable_init_unchained() internal onlyInitializing {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../../utils/AddressUpgradeable.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n * @custom:oz-retyped-from bool\n */\n uint8 private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint8 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts.\n *\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\n * constructor.\n *\n * Emits an {Initialized} event.\n */\n modifier initializer() {\n bool isTopLevelCall = !_initializing;\n require(\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\n \"Initializable: contract is already initialized\"\n );\n _initialized = 1;\n if (isTopLevelCall) {\n _initializing = true;\n }\n _;\n if (isTopLevelCall) {\n _initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\n * are added through upgrades and that require initialization.\n *\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\n * cannot be nested. If one is invoked in the context of another, execution will revert.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n *\n * WARNING: setting the version to 255 will prevent any future reinitialization.\n *\n * Emits an {Initialized} event.\n */\n modifier reinitializer(uint8 version) {\n require(!_initializing && _initialized < version, \"Initializable: contract is already initialized\");\n _initialized = version;\n _initializing = true;\n _;\n _initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n *\n * Emits an {Initialized} event the first time it is successfully executed.\n */\n function _disableInitializers() internal virtual {\n require(!_initializing, \"Initializable: contract is initializing\");\n if (_initialized != type(uint8).max) {\n _initialized = type(uint8).max;\n emit Initialized(type(uint8).max);\n }\n }\n\n /**\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\n */\n function _getInitializedVersion() internal view returns (uint8) {\n return _initialized;\n }\n\n /**\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\n */\n function _isInitializing() internal view returns (bool) {\n return _initializing;\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract PausableUpgradeable is Initializable, ContextUpgradeable {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n function __Pausable_init() internal onlyInitializing {\n __Pausable_init_unchained();\n }\n\n function __Pausable_init_unchained() internal onlyInitializing {\n _paused = false;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n _requireNotPaused();\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n _requirePaused();\n _;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Throws if the contract is paused.\n */\n function _requireNotPaused() internal view virtual {\n require(!paused(), \"Pausable: paused\");\n }\n\n /**\n * @dev Throws if the contract is not paused.\n */\n function _requirePaused() internal view virtual {\n require(paused(), \"Pausable: not paused\");\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary AddressUpgradeable {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n *\n * Furthermore, `isContract` will also return true if the target contract within\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\n * which only has an effect at the end of a transaction.\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"./extensions/IERC20Metadata.sol\";\nimport \"../../utils/Context.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(address from, address to, uint256 amount) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(address owner, address spender, uint256 amount) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\n\nimport \"./IAccessControlManagerV8.sol\";\n\n/**\n * @title AccessControlledV8\n * @author Venus\n * @notice This contract is helper between access control manager and actual contract. This contract further inherited by other contract (using solidity 0.8.13)\n * to integrate access controlled mechanism. It provides initialise methods and verifying access methods.\n */\nabstract contract AccessControlledV8 is Initializable, Ownable2StepUpgradeable {\n /// @notice Access control manager contract\n IAccessControlManagerV8 internal _accessControlManager;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n\n /// @notice Emitted when access control manager contract address is changed\n event NewAccessControlManager(address oldAccessControlManager, address newAccessControlManager);\n\n /// @notice Thrown when the action is prohibited by AccessControlManager\n error Unauthorized(address sender, address calledContract, string methodSignature);\n\n function __AccessControlled_init(address accessControlManager_) internal onlyInitializing {\n __Ownable2Step_init();\n __AccessControlled_init_unchained(accessControlManager_);\n }\n\n function __AccessControlled_init_unchained(address accessControlManager_) internal onlyInitializing {\n _setAccessControlManager(accessControlManager_);\n }\n\n /**\n * @notice Sets the address of AccessControlManager\n * @dev Admin function to set address of AccessControlManager\n * @param accessControlManager_ The new address of the AccessControlManager\n * @custom:event Emits NewAccessControlManager event\n * @custom:access Only Governance\n */\n function setAccessControlManager(address accessControlManager_) external onlyOwner {\n _setAccessControlManager(accessControlManager_);\n }\n\n /**\n * @notice Returns the address of the access control manager contract\n */\n function accessControlManager() external view returns (IAccessControlManagerV8) {\n return _accessControlManager;\n }\n\n /**\n * @dev Internal function to set address of AccessControlManager\n * @param accessControlManager_ The new address of the AccessControlManager\n */\n function _setAccessControlManager(address accessControlManager_) internal {\n require(address(accessControlManager_) != address(0), \"invalid acess control manager address\");\n address oldAccessControlManager = address(_accessControlManager);\n _accessControlManager = IAccessControlManagerV8(accessControlManager_);\n emit NewAccessControlManager(oldAccessControlManager, accessControlManager_);\n }\n\n /**\n * @notice Reverts if the call is not allowed by AccessControlManager\n * @param signature Method signature\n */\n function _checkAccessAllowed(string memory signature) internal view {\n bool isAllowedToCall = _accessControlManager.isAllowedToCall(msg.sender, signature);\n\n if (!isAllowedToCall) {\n revert Unauthorized(msg.sender, address(this), signature);\n }\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/access/IAccessControl.sol\";\n\n/**\n * @title IAccessControlManagerV8\n * @author Venus\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\n */\ninterface IAccessControlManagerV8 is IAccessControl {\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\n\n function revokeCallPermission(\n address contractAddress,\n string calldata functionSig,\n address accountToRevoke\n ) external;\n\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\n\n function hasPermission(\n address account,\n address contractAddress,\n string calldata functionSig\n ) external view returns (bool);\n}\n" + }, + "@venusprotocol/solidity-utilities/contracts/constants.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\nuint256 constant EXP_SCALE = 1e18;\n\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\nuint256 constant MANTISSA_ONE = EXP_SCALE;\n\n/// @dev The approximate number of seconds per year\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\n" + }, + "@venusprotocol/solidity-utilities/contracts/validators.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\nerror ZeroAddressNotAllowed();\n\n/// @notice Thrown if the supplied value is 0 where it is not allowed\nerror ZeroValueNotAllowed();\n\n/// @notice Checks if the provided address is nonzero, reverts otherwise\n/// @param address_ Address to check\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\nfunction ensureNonzeroAddress(address address_) pure {\n if (address_ == address(0)) {\n revert ZeroAddressNotAllowed();\n }\n}\n\n/// @notice Checks if the provided value is nonzero, reverts otherwise\n/// @param value_ Value to check\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\nfunction ensureNonzeroValue(uint256 value_) pure {\n if (value_ == 0) {\n revert ZeroValueNotAllowed();\n }\n}\n" + }, + "contracts/DeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { VBep20Interface } from \"./interfaces/VBep20Interface.sol\";\nimport { ResilientOracleInterface } from \"./interfaces/OracleInterface.sol\";\nimport { IDeviationBoundedOracle } from \"./interfaces/IDeviationBoundedOracle.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { Transient } from \"./lib/Transient.sol\";\n\n/**\n * @title DeviationBoundedOracle\n * @author Venus\n * @notice The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations.\n *\n * It maintains a per-market rolling min/max price window. When the current spot price deviates\n * significantly from the window bounds, protection mode activates automatically and conservative\n * pricing kicks in:\n * - Collateral is valued at min(spot, windowMin) — caps collateral value at recent window low\n * - Debt is valued at max(spot, windowMax) — floors debt value at recent window high\n *\n * This protects against instantaneous or short-duration price manipulation attacks on low-liquidity\n * collateral tokens. Sustained attacks beyond the window period are expected to be handled by\n * off-chain monitoring systems.\n *\n * The oracle exposes both view and non-view price functions. The non-view variants update the\n * price window and trigger protection. The view variants read stored state only. A transient\n * price cache avoids redundant ResilientOracle calls within the same transaction when\n * updateProtectionState is called before the view price reads.\n */\ncontract DeviationBoundedOracle is AccessControlledV8, IDeviationBoundedOracle {\n /// @notice Minimum allowed threshold value (5%) to account for keeper deadband\n uint256 public constant MIN_THRESHOLD = 5e16;\n\n /// @notice Maximum allowed threshold value (50%)\n uint256 public constant MAX_THRESHOLD = 50e16;\n\n /// @notice Keeper deadband threshold (5%) — min/max corrections below this are suppressed\n uint256 public constant KEEPER_DEADBAND = 5e16;\n\n /// @notice Resilient Oracle used to fetch spot prices\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Native market address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable nativeMarket;\n\n /// @notice VAI address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vai;\n\n /// @notice Transient storage slot for caching final collateral prices within a transaction\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/collateralCache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/collateralCache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant COLLATERAL_PRICE_CACHE_SLOT =\n 0x7bd9fcecef8429101f34baefb335883a97edd91e0d8fdc455d73ab727abf7000;\n\n /// @notice Transient storage slot for caching final debt prices within a transaction\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant DEBT_PRICE_CACHE_SLOT = 0x84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb8970600;\n\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\n /// and can serve as any underlying asset of a market that supports native tokens\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Per-asset protection state\n mapping(address => MarketProtectionState) public assetProtectionConfig;\n\n /// @notice Append-only array of all assets ever initialized, used for enumeration\n address[] public allAssets;\n\n /// @notice Storage gap for upgrades\n uint256[48] private __gap;\n\n /**\n * @notice Constructor for the implementation contract. Sets immutable variables.\n * @param _resilientOracle Address of the ResilientOracle contract\n * @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\n * @param vaiAddress The address of the VAI token, or address(0) if VAI is not deployed on the chain.\n * @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(ResilientOracleInterface _resilientOracle, address nativeMarketAddress, address vaiAddress) {\n ensureNonzeroAddress(address(_resilientOracle));\n ensureNonzeroAddress(nativeMarketAddress);\n RESILIENT_ORACLE = _resilientOracle;\n nativeMarket = nativeMarketAddress;\n vai = vaiAddress;\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n // ----- Non-view price functions (update window + trigger protection) -----\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns the conservative (lower) price when protection is active.\n * Used by keepers or direct callers who want atomic update + read.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice) {\n (collateralPrice, ) = _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns the conservative (higher) price when protection is active.\n * Used by keepers or direct callers who want atomic update + read.\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice) {\n (, debtPrice) = _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns both conservative prices in a single call.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice) {\n return _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Fetches the spot price, updates the protection window, and caches the resolved\n * collateral and debt prices in transient storage for the duration of the transaction.\n * @dev Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before\n * liquidity calculations). Subsequent calls to getBoundedCollateralPriceView /\n * getBoundedDebtPriceView within the same transaction will read from the transient cache\n * instead of querying ResilientOracle again, keeping those functions as `view` and\n * avoiding redundant oracle calls.\n * The transient cache is only populated when the asset's `cachingEnabled` flag is `true`.\n * When caching is disabled, view price reads fall through to live recomputation.\n * Permissionless: anyone can call this, both for gas optimisation and to ensure every\n * caller in the same transaction reads the correct, up-to-date bounded price.\n * @param vToken vToken address\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external {\n _updateAndGetBoundedPrices(vToken);\n }\n\n // ----- View price functions (read stored/cached state only) -----\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`\n * (populated by a prior updateProtectionState call in the same transaction). Falls back\n * to ResilientOracle on cache miss or when caching is disabled.\n * Returns min(spot, windowMin) when protection is active, spot otherwise.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 collateralPrice) {\n (collateralPrice, ) = _computeBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`\n * (populated by a prior updateProtectionState call in the same transaction). Falls back\n * to ResilientOracle on cache miss or when caching is disabled.\n * Returns max(spot, windowMax) when protection is active, spot otherwise.\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 debtPrice) {\n (, debtPrice) = _computeBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice) {\n return _computeBoundedPrices(vToken);\n }\n\n // ----- Keeper functions -----\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @dev Called by the keeper to push corrected min values from the off-chain sliding window.\n * Constraint: newMin must be at or below the current spot price.\n * @param asset The underlying asset address\n * @param newMin The new minimum price\n * @custom:access Only authorized keeper addresses\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external {\n _checkAccessAllowed(\"updateMinPrice(address,uint128)\");\n _validateAndUpdateBound(asset, newMin, PriceBoundType.MIN);\n }\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @dev Called by the keeper to push corrected max values from the off-chain sliding window.\n * Constraint: newMax must be at or above the current spot price.\n * @param asset The underlying asset address\n * @param newMax The new maximum price\n * @custom:access Only authorized keeper addresses\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external {\n _checkAccessAllowed(\"updateMaxPrice(address,uint128)\");\n _validateAndUpdateBound(asset, newMax, PriceBoundType.MAX);\n }\n\n /**\n * @notice Exits protection mode for a given asset\n * @dev Called by the keeper/monitor after confirming price has normalised.\n * Enforces two conditions on-chain:\n * 1. Cooldown period has elapsed since the last trigger\n * 2. Price range has converged below the exit threshold\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\n * @custom:error PriceRangeNotConverged if window range is still above exit threshold\n * @custom:event ProtectionModeExited\n */\n function exitProtectionMode(address asset) external {\n _checkAccessAllowed(\"exitProtectionMode(address)\");\n _exitProtectionMode(asset);\n }\n\n /**\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\n * Empty `actions` is a no-op success.\n * @param actions The list of keeper actions to apply\n * @custom:access Only authorized keeper addresses\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\n */\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external {\n _checkAccessAllowed(\"syncPriceBoundsAndProtections((address,uint8,uint256)[])\");\n uint256 len = actions.length;\n for (uint256 i; i < len; ++i) {\n KeeperActionItem calldata item = actions[i];\n if (item.action == KeeperAction.SetMinPrice) {\n _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MIN);\n } else if (item.action == KeeperAction.SetMaxPrice) {\n _validateAndUpdateBound(item.asset, _safeToUint128(item.value), PriceBoundType.MAX);\n } else if (item.action == KeeperAction.ExitProtectionMode) {\n _exitProtectionMode(item.asset);\n } else {\n revert InvalidKeeperAction(uint8(item.action));\n }\n }\n }\n\n // ----- Admin functions (governance-gated) -----\n\n /**\n * @notice Initializes protection for a new asset\n * @param tokenConfig_ Token config input for the asset\n * @custom:access Only Governance\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external {\n _checkAccessAllowed(\"setTokenConfig((address,uint64,uint256,uint256,bool,bool))\");\n _setTokenConfig(\n tokenConfig_.asset,\n tokenConfig_.cooldownPeriod,\n tokenConfig_.triggerThreshold,\n tokenConfig_.resetThreshold,\n tokenConfig_.enableBoundedPricing,\n tokenConfig_.enableCaching\n );\n }\n\n /**\n * @notice Batch-initializes protection for multiple assets in a single transaction\n * @param tokenConfigs_ Array of token config inputs, one per asset\n * @custom:access Only Governance\n * @custom:error InvalidArrayLength if the input array is empty\n * @custom:event ProtectionInitialized for each asset\n * @custom:event BoundedPricingWhitelistUpdated for each asset\n */\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external {\n _checkAccessAllowed(\"setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])\");\n uint256 len = tokenConfigs_.length;\n if (len == 0) revert InvalidArrayLength();\n\n for (uint256 i; i < len; ++i) {\n TokenConfigInput calldata tokenConfig = tokenConfigs_[i];\n _setTokenConfig(\n tokenConfig.asset,\n tokenConfig.cooldownPeriod,\n tokenConfig.triggerThreshold,\n tokenConfig.resetThreshold,\n tokenConfig.enableBoundedPricing,\n tokenConfig.enableCaching\n );\n }\n }\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds\n * @custom:access Only Governance\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external {\n _checkAccessAllowed(\"setCooldownPeriod(address,uint64)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(newCooldown);\n\n MarketProtectionState storage state = _ensureInitialized(asset);\n emit CooldownPeriodSet(asset, state.cooldownPeriod, newCooldown);\n state.cooldownPeriod = newCooldown;\n }\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external {\n _checkAccessAllowed(\"setThresholds(address,uint256,uint256)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(newTriggerThreshold);\n ensureNonzeroValue(newResetThreshold);\n if (newTriggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(newTriggerThreshold, MIN_THRESHOLD);\n if (newTriggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(newTriggerThreshold, MAX_THRESHOLD);\n if (newResetThreshold >= newTriggerThreshold) revert InvalidResetThreshold(newResetThreshold);\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (newTriggerThreshold != state.triggerThreshold) {\n emit TriggerThresholdSet(asset, state.triggerThreshold, newTriggerThreshold);\n state.triggerThreshold = uint128(newTriggerThreshold);\n }\n if (newResetThreshold != state.resetThreshold) {\n emit ResetThresholdSet(asset, state.resetThreshold, newResetThreshold);\n state.resetThreshold = uint128(newResetThreshold);\n }\n }\n\n /**\n * @notice Sets whether an asset is enabled for bounded pricing\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external {\n _checkAccessAllowed(\"setAssetBoundedPricingEnabled(address,bool)\");\n ensureNonzeroAddress(asset);\n\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (!enabled && state.currentlyUsingProtectedPrice) {\n revert ProtectedPriceActive(asset);\n }\n\n if (state.isBoundedPricingEnabled == enabled) return;\n\n // reset the window if re-enabling\n if (enabled) {\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\n _setMinPrice(state, asset, spotU128);\n _setMaxPrice(state, asset, spotU128);\n }\n\n state.isBoundedPricingEnabled = enabled;\n emit BoundedPricingWhitelistUpdated(asset, enabled);\n }\n\n /**\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\n * live spot instead of reading or writing the transient slots. The initial value is\n * set via the `enableCaching` argument of `setTokenConfig`.\n * @param asset The underlying asset address\n * @param enabled Whether transient caching is enabled for this asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CachingEnabledUpdated\n */\n function setCachingEnabled(address asset, bool enabled) external {\n _checkAccessAllowed(\"setCachingEnabled(address,bool)\");\n MarketProtectionState storage state = _ensureInitialized(asset);\n emit CachingEnabledUpdated(asset, state.cachingEnabled, enabled);\n state.cachingEnabled = enabled;\n }\n\n // ----- View helpers -----\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory) {\n return allAssets;\n }\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool) {\n return assetProtectionConfig[asset].isBoundedPricingEnabled;\n }\n\n /**\n * @notice Checks if the asset is currently using the protected (bounded) price\n * @param asset The underlying asset address\n * @return True if the asset is currently using the protected price instead of spot\n */\n function currentlyUsingProtectedPrice(address asset) external view returns (bool) {\n return assetProtectionConfig[asset].currentlyUsingProtectedPrice;\n }\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @dev Iterates the append-only allAssets array and filters by isBoundedPricingEnabled.\n * Gas-free for off-chain callers.\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory) {\n uint256 len = allAssets.length;\n address[] memory temp = new address[](len);\n uint256 count;\n for (uint256 i; i < len; ++i) {\n if (assetProtectionConfig[allAssets[i]].isBoundedPricingEnabled) {\n temp[count++] = allAssets[i];\n }\n }\n address[] memory result = new address[](count);\n for (uint256 i; i < count; ++i) {\n result[i] = temp[i];\n }\n return result;\n }\n\n /**\n * @notice Checks if protection can be exited for an asset\n * @dev Returns true when both conditions are met:\n * 1. Cooldown period has elapsed since last trigger\n * 2. Price range has converged below exit threshold\n * @param asset The underlying asset address\n * @return True if protection can be disabled\n */\n function canExitProtection(address asset) external view returns (bool) {\n MarketProtectionState storage state = assetProtectionConfig[asset];\n return\n state.currentlyUsingProtectedPrice &&\n block.timestamp >= uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod) &&\n _computePriceBoundRatio(state.minPrice, state.maxPrice) < state.resetThreshold;\n }\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the deadband\n * from the keeper's proposed window values\n * @dev Allows the keeper to identify stale windows in a single call, avoiding N individual reads.\n * Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's off-chain window minimum prices\n * @param proposedMaxs Keeper's off-chain window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate) {\n uint256 len = assets.length;\n if (len != proposedMins.length || len != proposedMaxs.length) revert InvalidArrayLength();\n\n needsMinUpdate = new bool[](len);\n needsMaxUpdate = new bool[](len);\n\n for (uint256 i; i < len; ++i) {\n MarketProtectionState storage state = assetProtectionConfig[assets[i]];\n needsMinUpdate[i] = _exceedsCorrectionDeadband(state.minPrice, proposedMins[i]);\n needsMaxUpdate[i] = _exceedsCorrectionDeadband(state.maxPrice, proposedMaxs[i]);\n }\n }\n\n // ----- Internal functions -----\n\n /**\n * @notice Initializes protection parameters and price window for a single asset\n * @dev Fetches the current spot price from ResilientOracle to seed the initial min/max window,\n * confirming the oracle is live for this asset before it is listed. Both bounds start at\n * spot so the window expands naturally as prices move. Can only be called once per asset.\n * @param asset The underlying asset address\n * @param cooldownPeriod Minimum time protection stays active after last trigger\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\n * @param enableBoundedPricing Whether to enable bounded pricing immediately upon initialization\n * @param enableCaching Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n */\n function _setTokenConfig(\n address asset,\n uint64 cooldownPeriod,\n uint256 triggerThreshold,\n uint256 resetThreshold,\n bool enableBoundedPricing,\n bool enableCaching\n ) internal {\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(cooldownPeriod);\n ensureNonzeroValue(triggerThreshold);\n ensureNonzeroValue(resetThreshold);\n if (assetProtectionConfig[asset].asset != address(0)) revert MarketAlreadyInitialized(asset);\n if (triggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(triggerThreshold, MIN_THRESHOLD);\n if (triggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(triggerThreshold, MAX_THRESHOLD);\n if (resetThreshold >= triggerThreshold) revert InvalidResetThreshold(resetThreshold);\n if (asset == vai) revert VAINotAllowed();\n\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\n\n assetProtectionConfig[asset] = MarketProtectionState({\n minPrice: spotU128,\n maxPrice: spotU128,\n currentlyUsingProtectedPrice: false,\n isBoundedPricingEnabled: enableBoundedPricing,\n lastProtectionTriggeredAt: 0,\n cooldownPeriod: cooldownPeriod,\n asset: asset,\n triggerThreshold: uint128(triggerThreshold),\n resetThreshold: uint128(resetThreshold),\n cachingEnabled: enableCaching\n });\n\n allAssets.push(asset);\n\n emit ProtectionInitialized(asset, spotU128, spotU128, cooldownPeriod, triggerThreshold);\n emit BoundedPricingWhitelistUpdated(asset, enableBoundedPricing);\n }\n\n /**\n * @notice Validates and applies a keeper-provided min or max price update\n * @param asset The underlying asset address\n * @param newPrice The new price value to set\n * @param boundType Whether this is a MIN or MAX bound update\n * @custom:error ZeroPriceNotAllowed if newPrice is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if boundType is MIN and newPrice exceeds the current spot or is strictly above maxPrice\n * @custom:error InvalidMaxPrice if boundType is MAX and newPrice is below the current spot or is strictly below minPrice\n */\n function _validateAndUpdateBound(address asset, uint128 newPrice, PriceBoundType boundType) internal {\n ensureNonzeroAddress(asset);\n if (newPrice == 0) revert ZeroPriceNotAllowed();\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n uint256 currentSpot = _fetchSpotPrice(asset);\n if (boundType == PriceBoundType.MIN) {\n if (newPrice > state.maxPrice || uint256(newPrice) > currentSpot)\n revert InvalidMinPrice(asset, newPrice, currentSpot);\n _setMinPrice(state, asset, newPrice);\n } else if (boundType == PriceBoundType.MAX) {\n if (newPrice < state.minPrice || uint256(newPrice) < currentSpot)\n revert InvalidMaxPrice(asset, newPrice, currentSpot);\n _setMaxPrice(state, asset, newPrice);\n }\n }\n\n /**\n * @notice Clears protection for an asset once cooldown has elapsed and the window has converged\n * @dev Shared body of `exitProtectionMode` and the ExitProtectionMode branch of `syncPriceBoundsAndProtections`.\n * Callers are responsible for ACM gating before invoking this helper.\n * @param asset The underlying asset address\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\n */\n function _exitProtectionMode(address asset) internal {\n ensureNonzeroAddress(asset);\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (!state.currentlyUsingProtectedPrice) revert ProtectedPriceInactive(asset);\n\n if (block.timestamp < uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod)) {\n revert CooldownNotElapsed(asset, state.lastProtectionTriggeredAt, state.cooldownPeriod);\n }\n\n uint256 rangeRatio = _computePriceBoundRatio(state.minPrice, state.maxPrice);\n if (rangeRatio >= state.resetThreshold) {\n revert PriceRangeNotConverged(asset, rangeRatio, state.resetThreshold);\n }\n\n state.currentlyUsingProtectedPrice = false;\n state.lastProtectionTriggeredAt = 0;\n emit ProtectionModeExited(asset);\n }\n\n /**\n * @notice Shared non-view logic for all bounded price functions.\n * Fetches spot, updates window, triggers protection if needed, and returns both bounded prices.\n * @param vToken vToken address\n * @return minPrice The bounded lower (collateral) price\n * @return maxPrice The bounded upper (debt) price\n */\n function _updateAndGetBoundedPrices(address vToken) internal returns (uint256 minPrice, uint256 maxPrice) {\n address asset = _getUnderlyingAsset(vToken);\n\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\n (minPrice, maxPrice) = _getCachedPrices(asset);\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\n\n // return early if failure from resilient oracle to prevent cold SLOAD\n uint256 spot = _fetchSpotPrice(asset);\n MarketProtectionState storage state = assetProtectionConfig[asset];\n if (!state.isBoundedPricingEnabled) {\n _setCachedPrices(asset, spot, spot);\n return (spot, spot);\n }\n (uint128 updatedMin, uint128 updatedMax, bool windowExpanded) = _expandPriceWindow(state, spot, asset);\n bool protectionActive = _checkAndTriggerProtection(state, spot, asset, windowExpanded);\n (minPrice, maxPrice) = _resolveBoundedPrices(protectionActive, spot, uint256(updatedMin), uint256(updatedMax));\n _setCachedPrices(asset, minPrice, maxPrice);\n }\n\n /**\n * @dev Expands the price window toward extremes if the spot price is a new min or max\n * @param state The market protection state\n * @param spot The current spot price\n * @param asset The underlying asset address (for event emission)\n */\n function _expandPriceWindow(\n MarketProtectionState storage state,\n uint256 spot,\n address asset\n ) internal returns (uint128, uint128, bool) {\n uint128 spotU128 = _safeToUint128(spot);\n uint128 currentMin = state.minPrice;\n uint128 currentMax = state.maxPrice;\n bool windowExpanded;\n if (spotU128 < currentMin) {\n _setMinPrice(state, asset, spotU128);\n currentMin = spotU128;\n windowExpanded = true;\n }\n if (spotU128 > currentMax) {\n _setMaxPrice(state, asset, spotU128);\n currentMax = spotU128;\n windowExpanded = true;\n }\n return (currentMin, currentMax, windowExpanded);\n }\n\n /**\n * @dev Checks if the spot price has deviated beyond the threshold and triggers protection.\n * `lastProtectionTriggeredAt` is reset only on the first trigger or when the price has made a\n * genuine new extreme this update (windowExpanded == true). Recovery within the existing window\n * keeps the cooldown ticking so `exitProtectionMode` remains reachable.\n * @param state The market protection state\n * @param spot The current spot price\n * @param asset The underlying asset address (for event emission)\n * @param windowExpanded True if `_expandPriceWindow` recorded a new low or new high this call\n */\n function _checkAndTriggerProtection(\n MarketProtectionState storage state,\n uint256 spot,\n address asset,\n bool windowExpanded\n ) internal returns (bool triggered) {\n if (_exceedsDeviationThreshold(spot, state.minPrice, state.maxPrice, state.triggerThreshold)) {\n bool enteringProtection = !state.currentlyUsingProtectedPrice;\n if (enteringProtection || windowExpanded) {\n state.lastProtectionTriggeredAt = uint64(block.timestamp);\n }\n if (enteringProtection) {\n state.currentlyUsingProtectedPrice = true;\n }\n emit ProtectionTriggered(asset, spot, state.minPrice, state.maxPrice);\n return true;\n }\n if (state.currentlyUsingProtectedPrice) return true;\n }\n\n /**\n * @notice Resolves the final bounded collateral and debt prices given a spot, window bounds, and protection flag.\n * @dev When protection is active: collateral = min(spot, windowMin), debt = max(spot, windowMax).\n * When protection is inactive: both return spot.\n * @param protectionActive Whether the market protection window is currently active\n * @param spot The current spot price\n * @param windowMin The lower bound of the price window\n * @param windowMax The upper bound of the price window\n * @return minPrice The resolved lower-bound (collateral) price\n * @return maxPrice The resolved upper-bound (debt) price\n */\n function _resolveBoundedPrices(\n bool protectionActive,\n uint256 spot,\n uint256 windowMin,\n uint256 windowMax\n ) internal pure returns (uint256, uint256) {\n if (!protectionActive) return (spot, spot);\n return (spot < windowMin ? spot : windowMin, spot > windowMax ? spot : windowMax);\n }\n\n /**\n * @notice Shared view logic for all bounded price view functions.\n * Checks transient cache first for an early return; on miss, fetches from oracle\n * and computes both prices without state mutations.\n * @param vToken vToken address\n * @return minPrice The bounded lower (collateral) price\n * @return maxPrice The bounded upper (debt) price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function _computeBoundedPrices(address vToken) internal view returns (uint256 minPrice, uint256 maxPrice) {\n address asset = _getUnderlyingAsset(vToken);\n\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\n (minPrice, maxPrice) = _getCachedPrices(asset);\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\n\n // Cache miss — fetch from oracle and compute without state mutations\n uint256 spot = _fetchSpotPrice(asset);\n MarketProtectionState storage state = assetProtectionConfig[asset];\n if (!state.isBoundedPricingEnabled) return (spot, spot);\n\n // Mirror _expandPriceWindow logic: compute what the window would be after expansion\n uint128 spotU128 = _safeToUint128(spot);\n uint128 windowMin128 = spot < uint256(state.minPrice) ? spotU128 : state.minPrice;\n uint128 windowMax128 = spot > uint256(state.maxPrice) ? spotU128 : state.maxPrice;\n\n bool shouldProtect = state.currentlyUsingProtectedPrice ||\n _exceedsDeviationThreshold(spot, windowMin128, windowMax128, state.triggerThreshold);\n\n (minPrice, maxPrice) = _resolveBoundedPrices(shouldProtect, spot, uint256(windowMin128), uint256(windowMax128));\n }\n\n /**\n * @dev Computes the relative spread between the price window bounds as a ratio scaled by EXP_SCALE.\n * Formula: \\((maxPrice - minPrice) / minPrice\\), scaled by `EXP_SCALE`.\n * Used to measure how much the window has converged -- compared against `resetThreshold`\n * to determine whether the price window is tight enough to exit protection mode.\n * @param minPrice The minimum price in the window\n * @param maxPrice The maximum price in the window\n * @return The scaled bound ratio \\(((max - min) * EXP_SCALE) / min\\)\n */\n function _computePriceBoundRatio(uint128 minPrice, uint128 maxPrice) internal pure returns (uint256) {\n uint256 range = uint256(maxPrice) - uint256(minPrice);\n return (range * EXP_SCALE) / uint256(minPrice);\n }\n\n /**\n * @notice Checks whether the spot price has moved beyond the threshold relative to the\n * opposite window bound — i.e. `spot > minPrice * (1 + threshold)` or\n * `spot < maxPrice * (1 - threshold)`.\n * @dev Pump detection: spot > minPrice * (1 + threshold)\n * Crash detection: spot < maxPrice * (1 - threshold)\n * @param spot The current spot price\n * @param minPrice The minimum price in the window\n * @param maxPrice The maximum price in the window\n * @param threshold The deviation threshold (mantissa)\n * @return True if deviation is triggered\n */\n function _exceedsDeviationThreshold(\n uint256 spot,\n uint128 minPrice,\n uint128 maxPrice,\n uint256 threshold\n ) internal pure returns (bool) {\n uint256 upperBound = (uint256(minPrice) * (EXP_SCALE + threshold)) / EXP_SCALE;\n uint256 lowerBound = (uint256(maxPrice) * (EXP_SCALE - threshold)) / EXP_SCALE;\n return (spot > upperBound || spot < lowerBound);\n }\n\n /**\n * @dev Returns true if the relative drift between onChain and proposed exceeds KEEPER_DEADBAND\n * @param currentPrice The current on-chain price\n * @param proposedPrice The keeper's proposed price\n * @return True if drift exceeds deadband\n */\n function _exceedsCorrectionDeadband(uint128 currentPrice, uint128 proposedPrice) internal pure returns (bool) {\n if (currentPrice == 0 || proposedPrice == 0) return false;\n uint256 diff = currentPrice > proposedPrice\n ? uint256(currentPrice - proposedPrice)\n : uint256(proposedPrice - currentPrice);\n return (diff * EXP_SCALE) / uint256(currentPrice) > KEEPER_DEADBAND;\n }\n\n /**\n * @dev Sets the minimum price in the window and emits MinPriceUpdated\n * @param state The market protection state\n * @param asset The underlying asset address (for event emission)\n * @param newMin The new minimum price\n */\n function _setMinPrice(MarketProtectionState storage state, address asset, uint128 newMin) internal {\n emit MinPriceUpdated(asset, state.minPrice, newMin);\n state.minPrice = newMin;\n }\n\n /**\n * @dev Sets the maximum price in the window and emits MaxPriceUpdated\n * @param state The market protection state\n * @param asset The underlying asset address (for event emission)\n * @param newMax The new maximum price\n */\n function _setMaxPrice(MarketProtectionState storage state, address asset, uint128 newMax) internal {\n emit MaxPriceUpdated(asset, state.maxPrice, newMax);\n state.maxPrice = newMax;\n }\n\n /**\n * @dev Writes both lower and upper bounded prices to transient storage. No-ops when the\n * asset's `cachingEnabled` flag is `false`, so callers that disable caching always\n * fall through to live recomputation on subsequent reads.\n * @param asset The underlying asset address\n * @param minPrice The resolved lower (collateral) price to cache\n * @param maxPrice The resolved upper (debt) price to cache\n */\n function _setCachedPrices(address asset, uint256 minPrice, uint256 maxPrice) internal {\n if (!assetProtectionConfig[asset].cachingEnabled) return;\n Transient.cachePrice(COLLATERAL_PRICE_CACHE_SLOT, asset, minPrice);\n Transient.cachePrice(DEBT_PRICE_CACHE_SLOT, asset, maxPrice);\n }\n\n /**\n * @dev Reads a cached final price from transient storage. Returns `(0, 0)` when the\n * asset's `cachingEnabled` flag is `false`, which callers already treat as a cache\n * miss and handle via live recomputation.\n * @param asset The underlying asset address\n * @return minPrice The cached minimum price, or 0 on cache miss\n * @return maxPrice The cached maximum price, or 0 on cache miss\n */\n function _getCachedPrices(address asset) internal view returns (uint256 minPrice, uint256 maxPrice) {\n if (!assetProtectionConfig[asset].cachingEnabled) return (0, 0);\n minPrice = Transient.readCachedPrice(COLLATERAL_PRICE_CACHE_SLOT, asset);\n maxPrice = Transient.readCachedPrice(DEBT_PRICE_CACHE_SLOT, asset);\n }\n\n /**\n * @dev This function returns the underlying asset of a vToken\n * @param vToken vToken address\n * @return asset underlying asset address\n */\n function _getUnderlyingAsset(address vToken) private view returns (address asset) {\n ensureNonzeroAddress(vToken);\n if (vToken == nativeMarket) {\n asset = NATIVE_TOKEN_ADDR;\n } else if (vToken == vai) {\n asset = vai;\n } else {\n asset = VBep20Interface(vToken).underlying();\n }\n }\n\n /**\n * @dev Reverts if the market has not been initialized via setTokenConfig\n * @param asset The underlying asset address\n * @return state The market protection state storage pointer\n */\n function _ensureInitialized(address asset) internal view returns (MarketProtectionState storage state) {\n state = assetProtectionConfig[asset];\n if (state.asset == address(0)) revert MarketNotInitialized(asset);\n }\n\n /**\n * @notice Fetches the current spot price for an asset from the ResilientOracle\n * @param asset The underlying asset address\n * @return The current spot price\n */\n function _fetchSpotPrice(address asset) internal view returns (uint256) {\n return RESILIENT_ORACLE.getPrice(asset);\n }\n\n /**\n * @dev Safely casts a uint256 to uint128, reverting on overflow\n * @param value The value to cast\n * @return The value as uint128\n */\n function _safeToUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) revert PriceExceedsUint128(value);\n return uint128(value);\n }\n}\n" + }, + "contracts/hardhat-dependency-compiler/@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport '@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol';\n" + }, + "contracts/hardhat-dependency-compiler/hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport 'hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol';\n" + }, + "contracts/hardhat-dependency-compiler/hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport 'hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol';\n" + }, + "contracts/interfaces/FeedRegistryInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface FeedRegistryInterface {\n function latestRoundDataByName(\n string memory base,\n string memory quote\n )\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);\n\n function decimalsByName(string memory base, string memory quote) external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAccountant.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAccountant {\n function getRateSafe() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IAnkrBNB.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAnkrBNB {\n function sharesToBonds(uint256 amount) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAsBNB.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAsBNB {\n function minter() external view returns (address);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAsBNBMinter.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAsBNBMinter {\n function convertToTokens(uint256 amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICappedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ICappedOracle {\n function updateSnapshot() external;\n}\n" + }, + "contracts/interfaces/IDeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IDeviationBoundedOracle {\n // --- Enums ---\n\n /// @notice Identifies whether a price bound is a minimum or maximum\n enum PriceBoundType {\n MIN,\n MAX\n }\n\n /// @notice Identifies which keeper action a single syncPriceBoundsAndProtections item performs\n enum KeeperAction {\n SetMinPrice,\n SetMaxPrice,\n ExitProtectionMode\n }\n\n // --- Structs ---\n\n /// @notice Per-asset protection state tracking the min/max price window\n struct MarketProtectionState {\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\n uint128 minPrice;\n /// @notice Highest price observed in the current window\n uint128 maxPrice;\n /// @notice Whether protected price is currently being used\n bool currentlyUsingProtectedPrice;\n /// @notice Whether this market is whitelisted for bounded pricing\n bool isBoundedPricingEnabled;\n /// @notice Timestamp of the last protection trigger — reset on every trigger\n uint64 lastProtectionTriggeredAt;\n /// @notice Minimum time protection stays active after last trigger\n uint64 cooldownPeriod;\n /// @notice The underlying asset address, used to verify initialization\n address asset;\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\n uint128 triggerThreshold;\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\n uint128 resetThreshold;\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n bool cachingEnabled;\n }\n\n /// @notice One item in an syncPriceBoundsAndProtections payload\n /// @dev `value` is interpreted per-action: the new bound price for SetMinPrice / SetMaxPrice, ignored for ExitProtectionMode\n struct KeeperActionItem {\n address asset;\n KeeperAction action;\n uint256 value;\n }\n\n /// @notice One item in a setTokenConfigs payload\n struct TokenConfigInput {\n /// @notice The underlying asset address\n address asset;\n /// @notice Minimum time protection stays active after the last trigger (seconds)\n uint64 cooldownPeriod;\n /// @notice Entry deviation threshold (mantissa). Must be between 5% and 50%.\n uint256 triggerThreshold;\n /// @notice Exit deviation threshold (mantissa). Must be non-zero and below triggerThreshold.\n uint256 resetThreshold;\n /// @notice Whether to enable bounded pricing immediately upon initialization\n bool enableBoundedPricing;\n /// @notice Whether transient caching of the bounded (collateral, debt) pair is enabled for this asset\n bool enableCaching;\n }\n\n // --- Events ---\n\n /// @notice Emitted when protection is initialized for an asset\n event ProtectionInitialized(\n address indexed asset,\n uint128 minPrice,\n uint128 maxPrice,\n uint64 cooldownPeriod,\n uint256 triggerThreshold\n );\n\n /// @notice Emitted when protection mode is triggered for an asset\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\n\n /// @notice Emitted when protection mode is disabled for an asset\n event ProtectionModeExited(address indexed asset);\n\n /// @notice Emitted when the keeper updates the minimum price for an asset\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\n\n /// @notice Emitted when the keeper updates the maximum price for an asset\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\n\n /// @notice Emitted when the entry threshold is updated for an asset\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\n\n /// @notice Emitted when the exit threshold is updated for an asset\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\n\n /// @notice Emitted when the cooldown period is updated for an asset\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\n\n /// @notice Emitted when an asset's whitelist status changes\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\n\n /// @notice Emitted when the per-asset transient caching flag is toggled\n event CachingEnabledUpdated(address indexed asset, bool oldEnabled, bool newEnabled);\n\n // --- Errors ---\n\n /// @notice Thrown when trying to use or update protection for an asset that has not been initialized\n error MarketNotInitialized(address asset);\n\n /// @notice Thrown when trying to initialize an already initialized market\n error MarketAlreadyInitialized(address asset);\n\n /// @notice Thrown when trying to disable protection that is not active\n error ProtectedPriceInactive(address asset);\n\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\n\n /// @notice Thrown when trying to disable protection before price range has converged\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\n\n /// @notice Thrown when keeper tries to set minPrice above current spot\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\n\n /// @notice Thrown when keeper tries to set maxPrice below current spot\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\n\n /// @notice Thrown when threshold is set below the minimum allowed value\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\n\n /// @notice Thrown when threshold is set above the maximum allowed value\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\n\n /// @notice Thrown when a price exceeds uint128 max\n error PriceExceedsUint128(uint256 price);\n\n /// @notice Thrown when a zero price is provided where a non-zero price is required\n error ZeroPriceNotAllowed();\n\n /// @notice Thrown when trying to initialize protection for VAI\n error VAINotAllowed();\n\n /// @notice Thrown when trying to disable bounded pricing for an asset while protection is active\n error ProtectedPriceActive(address asset);\n\n /// @notice Thrown when the lengths of the arrays are not equal\n error InvalidArrayLength();\n\n /// @notice Thrown when the exit threshold is set at or above the trigger threshold\n error InvalidResetThreshold(uint256 resetThreshold);\n\n /// @notice Thrown when an syncPriceBoundsAndProtections item carries an unsupported action enum value\n error InvalidKeeperAction(uint8 action);\n\n // --- Non-view price functions (update window + trigger protection) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- State update (call before view price reads to populate transient cache) ---\n\n /**\n * @notice Updates the protection state for a given vToken, caching the resolved collateral and debt prices\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\n * reads in the same transaction are served from transient storage. The transient\n * cache is only populated when the asset's `cachingEnabled` flag is `true`.\n * @param vToken vToken address\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external;\n\n // --- View price functions (read stored/cached state only) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return price The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return price The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first when the asset's `cachingEnabled` flag is `true`;\n * falls back to ResilientOracle on cache miss or when caching is disabled.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- Keeper functions ---\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMin is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external;\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMax is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external;\n\n /**\n * @notice Exits protection mode for a given asset once conditions are met\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\n * @custom:event ProtectionModeExited\n */\n function exitProtectionMode(address asset) external;\n\n /**\n * @notice Dispatches a batch of keeper-only actions (set min, set max, or exit protection) under a single ACM check\n * @dev Each item is processed in array order; any item revert rolls back the whole batch.\n * `value` is interpreted as the new bound price for SetMinPrice / SetMaxPrice and ignored for ExitProtectionMode.\n * Empty `actions` is a no-op success.\n * @param actions The list of keeper actions to apply\n * @custom:access Only authorized keeper addresses\n * @custom:error InvalidKeeperAction if an item carries an unsupported action enum value\n * @custom:error PriceExceedsUint128 if a SetMin/SetMax item value overflows uint128\n * @custom:error ZeroPriceNotAllowed if a SetMin/SetMax item value is zero\n * @custom:error MarketNotInitialized if any referenced asset has not been initialized\n * @custom:error InvalidMinPrice if a SetMinPrice item violates the spot/maxPrice constraints\n * @custom:error InvalidMaxPrice if a SetMaxPrice item violates the spot/minPrice constraints\n * @custom:error ProtectedPriceInactive if an ExitProtectionMode item targets an asset whose protection is not active\n * @custom:error CooldownNotElapsed if an ExitProtectionMode item is submitted before cooldown elapsed\n * @custom:error PriceRangeNotConverged if an ExitProtectionMode item is submitted before window convergence\n * @custom:event MinPriceUpdated, MaxPriceUpdated, ProtectionModeExited\n */\n function syncPriceBoundsAndProtections(KeeperActionItem[] calldata actions) external;\n\n // --- Admin functions (governance-gated) ---\n\n /**\n * @notice Initializes protection parameters for a new asset\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\n * confirming the oracle is live for this asset before it is listed.\n * @param tokenConfig_ Token config input for the asset\n * @custom:access Only Governance\n * @custom:error ZeroAddressNotAllowed if asset is the zero address\n * @custom:error ZeroValueNotAllowed if cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(TokenConfigInput calldata tokenConfig_) external;\n\n /**\n * @notice Batch-initializes protection parameters for multiple assets in a single transaction\n * @param tokenConfigs_ Array of token config inputs, one per asset\n * @custom:access Only Governance\n * @custom:error InvalidArrayLength if the input array is empty\n * @custom:error ZeroAddressNotAllowed if any asset is the zero address\n * @custom:error ZeroValueNotAllowed if any cooldownPeriod, triggerThreshold, or resetThreshold is zero\n * @custom:error MarketAlreadyInitialized if any asset has already been initialized\n * @custom:error ThresholdBelowMinimum if any triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if any triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if any resetThreshold is at or above its triggerThreshold\n * @custom:error VAINotAllowed if any asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price for any asset overflows uint128\n * @custom:event ProtectionInitialized for each asset\n * @custom:event BoundedPricingWhitelistUpdated for each asset\n */\n function setTokenConfigs(TokenConfigInput[] calldata tokenConfigs_) external;\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds; must be non-zero\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\n\n /**\n * @notice Sets whether bounded pricing is enabled for an asset\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\n\n /**\n * @notice Toggles transient caching of the bounded (collateral, debt) pair for an asset\n * @dev When disabled, each view/non-view price call recomputes bounded prices from the\n * live spot instead of reading or writing the transient slots. The initial value is\n * set via the `enableCaching` argument of `setTokenConfig`.\n * @param asset The underlying asset address\n * @param enabled Whether transient caching is enabled for this asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CachingEnabledUpdated\n */\n function setCachingEnabled(address asset, bool enabled) external;\n\n // --- View helpers ---\n\n /**\n * @notice Returns the full protection state for an asset\n * @param asset The underlying asset address\n * @return minPrice Lowest price observed in the current window\n * @return maxPrice Highest price observed in the current window\n * @return currentlyUsingProtectedPrice Whether protected price is currently active\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\n * @return cooldownPeriod Minimum time protection stays active after last trigger\n * @return assetAddr The underlying asset address stored in the struct\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\n * @return cachingEnabled Whether transient caching of the bounded pair is enabled for the asset\n */\n function assetProtectionConfig(\n address asset\n )\n external\n view\n returns (\n uint128 minPrice,\n uint128 maxPrice,\n bool currentlyUsingProtectedPrice,\n bool isBoundedPricingEnabled,\n uint64 lastProtectionTriggeredAt,\n uint64 cooldownPeriod,\n address assetAddr,\n uint128 triggerThreshold,\n uint128 resetThreshold,\n bool cachingEnabled\n );\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool);\n\n /**\n * @notice Checks if the asset is currently using the protected (bounded) price\n * @param asset The underlying asset address\n * @return True if the asset is currently using the protected price instead of spot\n */\n function currentlyUsingProtectedPrice(address asset) external view returns (bool);\n\n /**\n * @notice Checks if protection can be exited for a given asset\n * @param asset The underlying asset address\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\n */\n function canExitProtection(address asset) external view returns (bool);\n\n /**\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\n * @param index Array index\n * @return The asset address at the given index\n */\n function allAssets(uint256 index) external view returns (address);\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory);\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's proposed window minimum prices\n * @param proposedMaxs Keeper's proposed window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\n}\n" + }, + "contracts/interfaces/IERC4626.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IERC4626 {\n function convertToAssets(uint256 shares) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IEtherFiLiquidityPool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IEtherFiLiquidityPool {\n function amountForShare(uint256 _share) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IPendlePtOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IPendlePtOracle {\n function getPtToAssetRate(address market, uint32 duration) external view returns (uint256);\n\n function getPtToSyRate(address market, uint32 duration) external view returns (uint256);\n\n function getOracleState(\n address market,\n uint32 duration\n )\n external\n view\n returns (bool increaseCardinalityRequired, uint16 cardinalityRequired, bool oldestObservationSatisfied);\n}\n" + }, + "contracts/interfaces/IPStakePool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IPStakePool {\n struct Data {\n uint256 totalWei;\n uint256 poolTokenSupply;\n }\n\n /**\n * @dev The current exchange rate for converting stkBNB to BNB.\n */\n function exchangeRate() external view returns (Data memory);\n}\n" + }, + "contracts/interfaces/ISFrax.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISFrax {\n function convertToAssets(uint256 shares) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/ISfrxEthFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISfrxEthFraxOracle {\n function getPrices() external view returns (bool _isbadData, uint256 _priceLow, uint256 _priceHigh);\n}\n" + }, + "contracts/interfaces/IStaderStakeManager.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IStaderStakeManager {\n function convertBnbXToBnb(uint256 _amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IStETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface IStETH {\n function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/ISynclubStakeManager.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISynclubStakeManager {\n function convertSnBnbToBnb(uint256 _amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IWBETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IWBETH {\n function exchangeRate() external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IZkETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface IZkETH {\n function LSTPerToken() external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/OracleInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface OracleInterface {\n function getPrice(address asset) external view returns (uint256);\n}\n\ninterface ResilientOracleInterface is OracleInterface {\n function updatePrice(address vToken) external;\n\n function updateAssetPrice(address asset) external;\n\n function getUnderlyingPrice(address vToken) external view returns (uint256);\n}\n\ninterface BoundValidatorInterface {\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool);\n}\n" + }, + "contracts/interfaces/PublicResolverInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity ^0.8.25;\n\ninterface PublicResolverInterface {\n function addr(bytes32 node) external view returns (address payable);\n}\n" + }, + "contracts/interfaces/SIDRegistryInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity ^0.8.25;\n\ninterface SIDRegistryInterface {\n function resolver(bytes32 node) external view returns (address);\n}\n" + }, + "contracts/interfaces/VBep20Interface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\ninterface VBep20Interface is IERC20Metadata {\n /**\n * @notice Underlying asset for this VToken\n */\n function underlying() external view returns (address);\n}\n" + }, + "contracts/lib/Transient.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nlibrary Transient {\n /**\n * @notice Cache the asset price into transient storage\n * @param key address of the asset\n * @param value asset price\n */\n function cachePrice(bytes32 cacheSlot, address key, uint256 value) internal {\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\n assembly (\"memory-safe\") {\n tstore(slot, value)\n }\n }\n\n /**\n * @notice Read cached price from transient storage\n * @param key address of the asset\n * @return value cached asset price\n */\n function readCachedPrice(bytes32 cacheSlot, address key) internal view returns (uint256 value) {\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\n assembly (\"memory-safe\") {\n value := tload(slot)\n }\n }\n}\n" + }, + "contracts/oracles/AnkrBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IAnkrBNB } from \"../interfaces/IAnkrBNB.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title AnkrBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of ankrBNB asset\n */\ncontract AnkrBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address ankrBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n ankrBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of BNB for 1 ankrBNB\n * @return amount The amount of BNB for ankrBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IAnkrBNB(CORRELATED_TOKEN).sharesToBonds(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/AsBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IAsBNB } from \"../interfaces/IAsBNB.sol\";\nimport { IAsBNBMinter } from \"../interfaces/IAsBNBMinter.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title asBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of asBNB asset\n */\ncontract AsBNBOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address asBNB,\n address slisBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n asBNB,\n slisBNB,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of slisBNB for 1 asBNB\n * @return price The amount of slisBNB for asBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n IAsBNBMinter minter = IAsBNBMinter(IAsBNB(CORRELATED_TOKEN).minter());\n return minter.convertToTokens(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/BinanceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/SIDRegistryInterface.sol\";\nimport \"../interfaces/FeedRegistryInterface.sol\";\nimport \"../interfaces/PublicResolverInterface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title BinanceOracle\n * @author Venus\n * @notice This oracle fetches price of assets from Binance.\n */\ncontract BinanceOracle is AccessControlledV8, OracleInterface {\n /// @notice Used to fetch feed registry address.\n address public sidRegistryAddress;\n\n /// @notice Set this as asset address for BNB. This is the underlying address for vBNB\n address public constant BNB_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Max stale period configuration for assets\n mapping(string => uint256) public maxStalePeriod;\n\n /// @notice Override symbols to be compatible with Binance feed registry\n mapping(string => string) public symbols;\n\n /// @notice Used to fetch price of assets used directly when space ID is not supported by current chain.\n address public feedRegistryAddress;\n\n /// @notice Emits when asset stale period is updated.\n event MaxStalePeriodAdded(string indexed asset, uint256 maxStalePeriod);\n\n /// @notice Emits when symbol of the asset is updated.\n event SymbolOverridden(string indexed symbol, string overriddenSymbol);\n\n /// @notice Emits when address of feed registry is updated.\n event FeedRegistryUpdated(address indexed oldFeedRegistry, address indexed newFeedRegistry);\n\n /**\n * @notice Checks whether an address is null or not\n */\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Sets the contracts required to fetch prices\n * @param _sidRegistryAddress Address of SID registry\n * @param _acm Address of the access control manager contract\n */\n function initialize(address _sidRegistryAddress, address _acm) external initializer {\n sidRegistryAddress = _sidRegistryAddress;\n __AccessControlled_init(_acm);\n }\n\n /**\n * @notice Used to set the max stale period of an asset\n * @param symbol The symbol of the asset\n * @param _maxStalePeriod The max stake period\n */\n function setMaxStalePeriod(string memory symbol, uint256 _maxStalePeriod) external {\n _checkAccessAllowed(\"setMaxStalePeriod(string,uint256)\");\n if (_maxStalePeriod == 0) revert(\"stale period can't be zero\");\n if (bytes(symbol).length == 0) revert(\"symbol cannot be empty\");\n\n maxStalePeriod[symbol] = _maxStalePeriod;\n emit MaxStalePeriodAdded(symbol, _maxStalePeriod);\n }\n\n /**\n * @notice Used to override a symbol when fetching price\n * @param symbol The symbol to override\n * @param overrideSymbol The symbol after override\n */\n function setSymbolOverride(string calldata symbol, string calldata overrideSymbol) external {\n _checkAccessAllowed(\"setSymbolOverride(string,string)\");\n if (bytes(symbol).length == 0) revert(\"symbol cannot be empty\");\n\n symbols[symbol] = overrideSymbol;\n emit SymbolOverridden(symbol, overrideSymbol);\n }\n\n /**\n * @notice Used to set feed registry address when current chain does not support space ID.\n * @param newfeedRegistryAddress Address of new feed registry.\n */\n function setFeedRegistryAddress(\n address newfeedRegistryAddress\n ) external notNullAddress(newfeedRegistryAddress) onlyOwner {\n if (sidRegistryAddress != address(0)) revert(\"sidRegistryAddress must be zero\");\n emit FeedRegistryUpdated(feedRegistryAddress, newfeedRegistryAddress);\n feedRegistryAddress = newfeedRegistryAddress;\n }\n\n /**\n * @notice Uses Space ID to fetch the feed registry address\n * @return feedRegistryAddress Address of binance oracle feed registry.\n */\n function getFeedRegistryAddress() public view returns (address) {\n bytes32 nodeHash = 0x94fe3821e0768eb35012484db4df61890f9a6ca5bfa984ef8ff717e73139faff;\n\n SIDRegistryInterface sidRegistry = SIDRegistryInterface(sidRegistryAddress);\n address publicResolverAddress = sidRegistry.resolver(nodeHash);\n PublicResolverInterface publicResolver = PublicResolverInterface(publicResolverAddress);\n\n return publicResolver.addr(nodeHash);\n }\n\n /**\n * @notice Gets the price of a asset from the binance oracle\n * @param asset Address of the asset\n * @return Price in USD\n */\n function getPrice(address asset) public view returns (uint256) {\n string memory symbol;\n uint256 decimals;\n\n if (asset == BNB_ADDR) {\n symbol = \"BNB\";\n decimals = 18;\n } else {\n IERC20Metadata token = IERC20Metadata(asset);\n symbol = token.symbol();\n decimals = token.decimals();\n }\n\n string memory overrideSymbol = symbols[symbol];\n\n if (bytes(overrideSymbol).length != 0) {\n symbol = overrideSymbol;\n }\n\n return _getPrice(symbol, decimals);\n }\n\n function _getPrice(string memory symbol, uint256 decimals) internal view returns (uint256) {\n FeedRegistryInterface feedRegistry;\n\n if (sidRegistryAddress != address(0)) {\n // If sidRegistryAddress is available, fetch feedRegistryAddress from sidRegistry\n feedRegistry = FeedRegistryInterface(getFeedRegistryAddress());\n } else {\n // Use feedRegistry directly if sidRegistryAddress is not available\n feedRegistry = FeedRegistryInterface(feedRegistryAddress);\n }\n\n (, int256 answer, , uint256 updatedAt, ) = feedRegistry.latestRoundDataByName(symbol, \"USD\");\n if (answer <= 0) revert(\"invalid binance oracle price\");\n if (block.timestamp < updatedAt) revert(\"updatedAt exceeds block time\");\n\n uint256 deltaTime;\n unchecked {\n deltaTime = block.timestamp - updatedAt;\n }\n if (deltaTime > maxStalePeriod[symbol]) revert(\"binance oracle price expired\");\n\n uint256 decimalDelta = feedRegistry.decimalsByName(symbol, \"USD\");\n return (uint256(answer) * (10 ** (18 - decimalDelta))) * (10 ** (18 - decimals));\n }\n}\n" + }, + "contracts/oracles/BNBxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IStaderStakeManager } from \"../interfaces/IStaderStakeManager.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title BNBxOracle\n * @author Venus\n * @notice This oracle fetches the price of BNBx asset\n */\ncontract BNBxOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakeManager\n IStaderStakeManager public immutable STAKE_MANAGER;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakeManager,\n address bnbx,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n bnbx,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakeManager);\n STAKE_MANAGER = IStaderStakeManager(stakeManager);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 BNBx\n * @return price The amount of BNB for BNBx\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STAKE_MANAGER.convertBnbXToBnb(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/BoundValidator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\n\n/**\n * @title BoundValidator\n * @author Venus\n * @notice The BoundValidator contract is used to validate prices fetched from two different sources.\n * Each asset has an upper and lower bound ratio set in the config. In order for a price to be valid\n * it must fall within this range of the validator price.\n */\ncontract BoundValidator is AccessControlledV8, BoundValidatorInterface {\n struct ValidateConfig {\n /// @notice asset address\n address asset;\n /// @notice Upper bound of deviation between reported price and anchor price,\n /// beyond which the reported price will be invalidated\n uint256 upperBoundRatio;\n /// @notice Lower bound of deviation between reported price and anchor price,\n /// below which the reported price will be invalidated\n uint256 lowerBoundRatio;\n }\n\n /// @notice validation configs by asset\n mapping(address => ValidateConfig) public validateConfigs;\n\n /// @notice Emit this event when new validation configs are added\n event ValidateConfigAdded(address indexed asset, uint256 indexed upperBound, uint256 indexed lowerBound);\n\n /// @notice Constructor for the implementation contract. Sets immutable variables.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the owner of the contract\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n /**\n * @notice Add multiple validation configs at the same time\n * @param configs Array of validation configs\n * @custom:access Only Governance\n * @custom:error Zero length error is thrown if length of the config array is 0\n * @custom:event Emits ValidateConfigAdded for each validation config that is successfully set\n */\n function setValidateConfigs(ValidateConfig[] memory configs) external {\n uint256 length = configs.length;\n if (length == 0) revert(\"invalid validate config length\");\n for (uint256 i; i < length; ++i) {\n setValidateConfig(configs[i]);\n }\n }\n\n /**\n * @notice Add a single validation config\n * @param config Validation config struct\n * @custom:access Only Governance\n * @custom:error Null address error is thrown if asset address is null\n * @custom:error Range error thrown if bound ratio is not positive\n * @custom:error Range error thrown if lower bound is greater than or equal to upper bound\n * @custom:event Emits ValidateConfigAdded when a validation config is successfully set\n */\n function setValidateConfig(ValidateConfig memory config) public {\n _checkAccessAllowed(\"setValidateConfig(ValidateConfig)\");\n\n if (config.asset == address(0)) revert(\"asset can't be zero address\");\n if (config.upperBoundRatio == 0 || config.lowerBoundRatio == 0) revert(\"bound must be positive\");\n if (config.upperBoundRatio <= config.lowerBoundRatio) revert(\"upper bound must be higher than lowner bound\");\n validateConfigs[config.asset] = config;\n emit ValidateConfigAdded(config.asset, config.upperBoundRatio, config.lowerBoundRatio);\n }\n\n /**\n * @notice Test reported asset price against anchor price\n * @param asset asset address\n * @param reportedPrice The price to be tested\n * @custom:error Missing error thrown if asset config is not set\n * @custom:error Price error thrown if anchor price is not valid\n */\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reportedPrice,\n uint256 anchorPrice\n ) public view virtual override returns (bool) {\n if (validateConfigs[asset].upperBoundRatio == 0) revert(\"validation config not exist\");\n if (anchorPrice == 0) revert(\"anchor price is not valid\");\n return _isWithinAnchor(asset, reportedPrice, anchorPrice);\n }\n\n /**\n * @notice Test whether the reported price is within the valid bounds\n * @param asset Asset address\n * @param reportedPrice The price to be tested\n * @param anchorPrice The reported price must be within the the valid bounds of this price\n */\n function _isWithinAnchor(address asset, uint256 reportedPrice, uint256 anchorPrice) private view returns (bool) {\n if (reportedPrice != 0) {\n // we need to multiply anchorPrice by 1e18 to make the ratio 18 decimals\n uint256 anchorRatio = (anchorPrice * 1e18) / reportedPrice;\n uint256 upperBoundAnchorRatio = validateConfigs[asset].upperBoundRatio;\n uint256 lowerBoundAnchorRatio = validateConfigs[asset].lowerBoundRatio;\n return anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio;\n }\n return false;\n }\n\n // BoundValidator is to get inherited, so it's a good practice to add some storage gaps like\n // OpenZepplin proposed in their contracts: https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n // solhint-disable-next-line\n uint256[49] private __gap;\n}\n" + }, + "contracts/oracles/ChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\n\n/**\n * @title ChainlinkOracle\n * @author Venus\n * @notice This oracle fetches prices of assets from the Chainlink oracle.\n */\ncontract ChainlinkOracle is AccessControlledV8, OracleInterface {\n struct TokenConfig {\n /// @notice Underlying token address, which can't be a null address\n /// @notice Used to check if a token is supported\n /// @notice 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB address for native tokens\n /// (e.g BNB for BNB chain, ETH for Ethereum network)\n address asset;\n /// @notice Chainlink feed address\n address feed;\n /// @notice Price expiration period of this asset\n uint256 maxStalePeriod;\n }\n\n /// @notice Set this as asset address for native token on each chain.\n /// This is the underlying address for vBNB on BNB chain or an underlying asset for a native market on any chain.\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Manually set an override price, useful under extenuating conditions such as price feed failure\n mapping(address => uint256) public prices;\n\n /// @notice Token config by assets\n mapping(address => TokenConfig) public tokenConfigs;\n\n /// @notice Emit when a price is manually set\n event PricePosted(address indexed asset, uint256 previousPriceMantissa, uint256 newPriceMantissa);\n\n /// @notice Emit when a token config is added\n event TokenConfigAdded(address indexed asset, address feed, uint256 maxStalePeriod);\n\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the owner of the contract\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n /**\n * @notice Manually set the price of a given asset\n * @param asset Asset address\n * @param price Asset price in 18 decimals\n * @custom:access Only Governance\n * @custom:event Emits PricePosted event on successfully setup of asset price\n */\n function setDirectPrice(address asset, uint256 price) external notNullAddress(asset) {\n _checkAccessAllowed(\"setDirectPrice(address,uint256)\");\n\n uint256 previousPriceMantissa = prices[asset];\n prices[asset] = price;\n emit PricePosted(asset, previousPriceMantissa, price);\n }\n\n /**\n * @notice Add multiple token configs at the same time\n * @param tokenConfigs_ config array\n * @custom:access Only Governance\n * @custom:error Zero length error thrown, if length of the array in parameter is 0\n */\n function setTokenConfigs(TokenConfig[] memory tokenConfigs_) external {\n if (tokenConfigs_.length == 0) revert(\"length can't be 0\");\n uint256 numTokenConfigs = tokenConfigs_.length;\n for (uint256 i; i < numTokenConfigs; ++i) {\n setTokenConfig(tokenConfigs_[i]);\n }\n }\n\n /**\n * @notice Add single token config. asset & feed cannot be null addresses and maxStalePeriod must be positive\n * @param tokenConfig Token config struct\n * @custom:access Only Governance\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error NotNullAddress error is thrown if token feed address is null\n * @custom:error Range error is thrown if maxStale period of token is not greater than zero\n * @custom:event Emits TokenConfigAdded event on successfully setting of the token config\n */\n function setTokenConfig(\n TokenConfig memory tokenConfig\n ) public notNullAddress(tokenConfig.asset) notNullAddress(tokenConfig.feed) {\n _checkAccessAllowed(\"setTokenConfig(TokenConfig)\");\n\n if (tokenConfig.maxStalePeriod == 0) revert(\"stale period can't be zero\");\n tokenConfigs[tokenConfig.asset] = tokenConfig;\n emit TokenConfigAdded(tokenConfig.asset, tokenConfig.feed, tokenConfig.maxStalePeriod);\n }\n\n /**\n * @notice Gets the price of a asset from the chainlink oracle\n * @param asset Address of the asset\n * @return Price in USD from Chainlink or a manually set price for the asset\n */\n function getPrice(address asset) public view virtual returns (uint256) {\n uint256 decimals;\n\n if (asset == NATIVE_TOKEN_ADDR) {\n decimals = 18;\n } else {\n IERC20Metadata token = IERC20Metadata(asset);\n decimals = token.decimals();\n }\n\n return _getPriceInternal(asset, decimals);\n }\n\n /**\n * @notice Gets the Chainlink price for a given asset\n * @param asset address of the asset\n * @param decimals decimals of the asset\n * @return price Asset price in USD or a manually set price of the asset\n */\n function _getPriceInternal(address asset, uint256 decimals) internal view returns (uint256 price) {\n uint256 tokenPrice = prices[asset];\n if (tokenPrice != 0) {\n price = tokenPrice;\n } else {\n price = _getChainlinkPrice(asset);\n }\n\n uint256 decimalDelta = 18 - decimals;\n return price * (10 ** decimalDelta);\n }\n\n /**\n * @notice Get the Chainlink price for an asset, revert if token config doesn't exist\n * @dev The precision of the price feed is used to ensure the returned price has 18 decimals of precision\n * @param asset Address of the asset\n * @return price Price in USD, with 18 decimals of precision\n * @custom:error NotNullAddress error is thrown if the asset address is null\n * @custom:error Price error is thrown if the Chainlink price of asset is not greater than zero\n * @custom:error Timing error is thrown if current timestamp is less than the last updatedAt timestamp\n * @custom:error Timing error is thrown if time difference between current time and last updated time\n * is greater than maxStalePeriod\n */\n function _getChainlinkPrice(\n address asset\n ) private view notNullAddress(tokenConfigs[asset].asset) returns (uint256) {\n TokenConfig memory tokenConfig = tokenConfigs[asset];\n AggregatorV3Interface feed = AggregatorV3Interface(tokenConfig.feed);\n\n // note: maxStalePeriod cannot be 0\n uint256 maxStalePeriod = tokenConfig.maxStalePeriod;\n\n // Chainlink USD-denominated feeds store answers at 8 decimals, mostly\n uint256 decimalDelta = 18 - feed.decimals();\n\n (, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();\n if (answer <= 0) revert(\"chainlink price must be positive\");\n if (block.timestamp < updatedAt) revert(\"updatedAt exceeds block time\");\n\n uint256 deltaTime;\n unchecked {\n deltaTime = block.timestamp - updatedAt;\n }\n\n if (deltaTime > maxStalePeriod) revert(\"chainlink price expired\");\n\n return uint256(answer) * (10 ** decimalDelta);\n }\n}\n" + }, + "contracts/oracles/common/CorrelatedTokenOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OracleInterface, ResilientOracleInterface } from \"../../interfaces/OracleInterface.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { SECONDS_PER_YEAR } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { ICappedOracle } from \"../../interfaces/ICappedOracle.sol\";\nimport { IAccessControlManagerV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\";\n\n/**\n * @title CorrelatedTokenOracle\n * @notice This oracle fetches the price of a token that is correlated to another token.\n */\nabstract contract CorrelatedTokenOracle is OracleInterface, ICappedOracle {\n /// @notice Address of the correlated token\n address public immutable CORRELATED_TOKEN;\n\n /// @notice Address of the underlying token\n address public immutable UNDERLYING_TOKEN;\n\n /// @notice Address of Resilient Oracle\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Address of the AccessControlManager contract\n IAccessControlManagerV8 public immutable ACCESS_CONTROL_MANAGER;\n\n //// @notice Growth rate percentage in seconds. Ex: 1e18 is 100%\n uint256 public growthRatePerSecond;\n\n /// @notice Snapshot update interval\n uint256 public snapshotInterval;\n\n /// @notice Last stored snapshot maximum exchange rate\n uint256 public snapshotMaxExchangeRate;\n\n /// @notice Last stored snapshot timestamp\n uint256 public snapshotTimestamp;\n\n /// @notice Gap to add when updating the snapshot\n uint256 public snapshotGap;\n\n /// @notice Emitted when the snapshot is updated\n event SnapshotUpdated(uint256 indexed maxExchangeRate, uint256 indexed timestamp);\n\n /// @notice Emitted when the growth rate is updated\n event GrowthRateUpdated(\n uint256 indexed oldGrowthRatePerSecond,\n uint256 indexed newGrowthRatePerSecond,\n uint256 indexed oldSnapshotInterval,\n uint256 newSnapshotInterval\n );\n\n /// @notice Emitted when the snapshot gap is updated\n event SnapshotGapUpdated(uint256 indexed oldSnapshotGap, uint256 indexed newSnapshotGap);\n\n /// @notice Thrown if the token address is invalid\n error InvalidTokenAddress();\n\n /// @notice Thrown if the growth rate is invalid\n error InvalidGrowthRate();\n\n /// @notice Thrown if the initial snapshot is invalid\n error InvalidInitialSnapshot();\n\n /// @notice Thrown if the max snapshot exchange rate is invalid\n error InvalidSnapshotMaxExchangeRate();\n\n /// @notice @notice Thrown when the action is prohibited by AccessControlManager\n error Unauthorized(address sender, address calledContract, string methodSignature);\n\n /**\n * @notice Constructor for the implementation contract.\n * @custom:error InvalidGrowthRate error is thrown if the growth rate is invalid\n * @custom:error InvalidInitialSnapshot error is thrown if the initial snapshot values are invalid\n */\n constructor(\n address _correlatedToken,\n address _underlyingToken,\n address _resilientOracle,\n uint256 _annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 _initialSnapshotMaxExchangeRate,\n uint256 _initialSnapshotTimestamp,\n address _accessControlManager,\n uint256 _snapshotGap\n ) {\n growthRatePerSecond = _annualGrowthRate / SECONDS_PER_YEAR;\n\n if ((growthRatePerSecond == 0 && _snapshotInterval > 0) || (growthRatePerSecond > 0 && _snapshotInterval == 0))\n revert InvalidGrowthRate();\n\n if ((_initialSnapshotMaxExchangeRate == 0 || _initialSnapshotTimestamp == 0) && _snapshotInterval > 0) {\n revert InvalidInitialSnapshot();\n }\n\n ensureNonzeroAddress(_correlatedToken);\n ensureNonzeroAddress(_underlyingToken);\n ensureNonzeroAddress(_resilientOracle);\n ensureNonzeroAddress(_accessControlManager);\n\n CORRELATED_TOKEN = _correlatedToken;\n UNDERLYING_TOKEN = _underlyingToken;\n RESILIENT_ORACLE = ResilientOracleInterface(_resilientOracle);\n snapshotInterval = _snapshotInterval;\n\n snapshotMaxExchangeRate = _initialSnapshotMaxExchangeRate;\n snapshotTimestamp = _initialSnapshotTimestamp;\n snapshotGap = _snapshotGap;\n\n ACCESS_CONTROL_MANAGER = IAccessControlManagerV8(_accessControlManager);\n }\n\n /**\n * @notice Directly sets the snapshot exchange rate and timestamp\n * @param _snapshotMaxExchangeRate The exchange rate to set\n * @param _snapshotTimestamp The timestamp to set\n * @custom:event Emits SnapshotUpdated event on successful update of the snapshot\n */\n function setSnapshot(uint256 _snapshotMaxExchangeRate, uint256 _snapshotTimestamp) external {\n _checkAccessAllowed(\"setSnapshot(uint256,uint256)\");\n\n snapshotMaxExchangeRate = _snapshotMaxExchangeRate;\n snapshotTimestamp = _snapshotTimestamp;\n\n emit SnapshotUpdated(snapshotMaxExchangeRate, snapshotTimestamp);\n }\n\n /**\n * @notice Sets the growth rate and snapshot interval\n * @param _annualGrowthRate The annual growth rate to set\n * @param _snapshotInterval The snapshot interval to set\n * @custom:error InvalidGrowthRate error is thrown if the growth rate is invalid\n * @custom:event Emits GrowthRateUpdated event on successful update of the growth rate\n */\n function setGrowthRate(uint256 _annualGrowthRate, uint256 _snapshotInterval) external {\n _checkAccessAllowed(\"setGrowthRate(uint256,uint256)\");\n uint256 oldGrowthRatePerSecond = growthRatePerSecond;\n\n growthRatePerSecond = _annualGrowthRate / SECONDS_PER_YEAR;\n\n if ((growthRatePerSecond == 0 && _snapshotInterval > 0) || (growthRatePerSecond > 0 && _snapshotInterval == 0))\n revert InvalidGrowthRate();\n\n emit GrowthRateUpdated(oldGrowthRatePerSecond, growthRatePerSecond, snapshotInterval, _snapshotInterval);\n\n snapshotInterval = _snapshotInterval;\n }\n\n /**\n * @notice Sets the snapshot gap\n * @param _snapshotGap The snapshot gap to set\n * @custom:event Emits SnapshotGapUpdated event on successful update of the snapshot gap\n */\n function setSnapshotGap(uint256 _snapshotGap) external {\n _checkAccessAllowed(\"setSnapshotGap(uint256)\");\n\n emit SnapshotGapUpdated(snapshotGap, _snapshotGap);\n\n snapshotGap = _snapshotGap;\n }\n\n /**\n * @notice Returns if the price is capped\n * @return isCapped Boolean indicating if the price is capped\n */\n function isCapped() external view virtual returns (bool) {\n if (snapshotInterval == 0) {\n return false;\n }\n\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n if (maxAllowedExchangeRate == 0) {\n return false;\n }\n\n uint256 exchangeRate = getUnderlyingAmount();\n\n return exchangeRate > maxAllowedExchangeRate;\n }\n\n /**\n * @notice Updates the snapshot price and timestamp\n * @custom:event Emits SnapshotUpdated event on successful update of the snapshot\n * @custom:error InvalidSnapshotMaxExchangeRate error is thrown if the max snapshot exchange rate is zero\n */\n function updateSnapshot() public override {\n if (block.timestamp - snapshotTimestamp < snapshotInterval || snapshotInterval == 0) return;\n\n uint256 exchangeRate = getUnderlyingAmount();\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n\n snapshotMaxExchangeRate =\n (exchangeRate > maxAllowedExchangeRate ? maxAllowedExchangeRate : exchangeRate) +\n snapshotGap;\n snapshotTimestamp = block.timestamp;\n\n if (snapshotMaxExchangeRate == 0) revert InvalidSnapshotMaxExchangeRate();\n\n RESILIENT_ORACLE.updateAssetPrice(UNDERLYING_TOKEN);\n emit SnapshotUpdated(snapshotMaxExchangeRate, snapshotTimestamp);\n }\n\n /**\n * @notice Fetches the price of the token\n * @param asset Address of the token\n * @return price The price of the token in scaled decimal places. It can be capped\n * to a maximum value taking into account the growth rate\n * @custom:error InvalidTokenAddress error is thrown if the token address is invalid\n */\n function getPrice(address asset) public view override returns (uint256) {\n if (asset != CORRELATED_TOKEN) revert InvalidTokenAddress();\n\n uint256 exchangeRate = getUnderlyingAmount();\n\n if (snapshotInterval == 0) {\n return _calculatePrice(exchangeRate);\n }\n\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n\n uint256 finalExchangeRate = (exchangeRate > maxAllowedExchangeRate && maxAllowedExchangeRate != 0)\n ? maxAllowedExchangeRate\n : exchangeRate;\n\n return _calculatePrice(finalExchangeRate);\n }\n\n /**\n * @notice Gets the maximum allowed exchange rate for token\n * @return maxExchangeRate Maximum allowed exchange rate\n */\n function getMaxAllowedExchangeRate() public view returns (uint256) {\n uint256 timeElapsed = block.timestamp - snapshotTimestamp;\n uint256 maxExchangeRate = snapshotMaxExchangeRate +\n (snapshotMaxExchangeRate * growthRatePerSecond * timeElapsed) /\n 1e18;\n return maxExchangeRate;\n }\n\n /**\n * @notice Gets the underlying amount for correlated token\n * @return underlyingAmount Amount of underlying token\n */\n function getUnderlyingAmount() public view virtual returns (uint256);\n\n /**\n * @notice Fetches price of the token based on an underlying exchange rate\n * @param exchangeRate The underlying exchange rate to use\n * @return price The price of the token in scaled decimal places\n */\n function _calculatePrice(uint256 exchangeRate) internal view returns (uint256) {\n uint256 underlyingUSDPrice = RESILIENT_ORACLE.getPrice(UNDERLYING_TOKEN);\n\n IERC20Metadata token = IERC20Metadata(CORRELATED_TOKEN);\n uint256 decimals = token.decimals();\n\n return (exchangeRate * underlyingUSDPrice) / (10 ** decimals);\n }\n\n /**\n * @notice Reverts if the call is not allowed by AccessControlManager\n * @param signature Method signature\n * @custom:error Unauthorized error is thrown if the call is not allowed\n */\n function _checkAccessAllowed(string memory signature) internal view {\n bool isAllowedToCall = ACCESS_CONTROL_MANAGER.isAllowedToCall(msg.sender, signature);\n\n if (!isAllowedToCall) {\n revert Unauthorized(msg.sender, address(this), signature);\n }\n }\n}\n" + }, + "contracts/oracles/ERC4626Oracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IERC4626 } from \"../interfaces/IERC4626.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title ERC4626Oracle\n * @author Venus\n * @notice This oracle fetches the price of ERC4626 tokens\n */\ncontract ERC4626Oracle is CorrelatedTokenOracle {\n uint256 public immutable ONE_CORRELATED_TOKEN;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ONE_CORRELATED_TOKEN = 10 ** IERC4626(correlatedToken).decimals();\n }\n\n /**\n * @notice Fetches the amount of underlying token for 1 correlated token\n * @return amount The amount of underlying token for correlated token\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IERC4626(CORRELATED_TOKEN).convertToAssets(ONE_CORRELATED_TOKEN);\n }\n}\n" + }, + "contracts/oracles/EtherfiAccountantOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IAccountant } from \"../interfaces/IAccountant.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title EtherfiAccountantOracle\n * @author Venus\n * @notice This oracle fetches the price of any Ether.fi asset that uses\n * Accountant contracts to derive the underlying price\n */\ncontract EtherfiAccountantOracle is CorrelatedTokenOracle {\n /// @notice Address of Accountant\n IAccountant public immutable ACCOUNTANT;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address accountant,\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(accountant);\n ACCOUNTANT = IAccountant(accountant);\n }\n\n /**\n * @notice Fetches the conversion rate from the ACCOUNTANT contract\n * @return amount Amount of WBTC\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ACCOUNTANT.getRateSafe();\n }\n}\n" + }, + "contracts/oracles/mocks/MockAccountant.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/IAccountant.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAccountant is IAccountant, Ownable {\n uint256 public rate;\n\n constructor() Ownable() {}\n\n function setRate(uint256 _rate) external onlyOwner {\n rate = _rate;\n }\n\n function getRateSafe() external view override returns (uint256) {\n return rate;\n }\n}\n" + }, + "contracts/oracles/mocks/MockBinanceFeedRegistry.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/FeedRegistryInterface.sol\";\n\ncontract MockBinanceFeedRegistry is FeedRegistryInterface {\n mapping(string => uint256) public assetPrices;\n\n function setAssetPrice(string memory base, uint256 price) external {\n assetPrices[base] = price;\n }\n\n function latestRoundDataByName(\n string memory base,\n string memory quote\n )\n external\n view\n override\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n quote;\n return (0, int256(assetPrices[base]), 0, block.timestamp - 10, 0);\n }\n\n function decimalsByName(string memory base, string memory quote) external view override returns (uint8) {\n return 8;\n }\n}\n" + }, + "contracts/oracles/mocks/MockBinanceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OwnableUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\nimport { OracleInterface } from \"../../interfaces/OracleInterface.sol\";\n\ncontract MockBinanceOracle is OwnableUpgradeable, OracleInterface {\n mapping(address => uint256) public assetPrices;\n\n constructor() {}\n\n function initialize() public initializer {}\n\n function setPrice(address asset, uint256 price) external {\n assetPrices[asset] = price;\n }\n\n function getPrice(address token) public view returns (uint256) {\n return assetPrices[token];\n }\n}\n" + }, + "contracts/oracles/mocks/MockChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OwnableUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\nimport { OracleInterface } from \"../../interfaces/OracleInterface.sol\";\n\ncontract MockChainlinkOracle is OwnableUpgradeable, OracleInterface {\n mapping(address => uint256) public assetPrices;\n\n //set price in 6 decimal precision\n constructor() {}\n\n function initialize() public initializer {\n __Ownable_init();\n }\n\n function setPrice(address asset, uint256 price) external {\n assetPrices[asset] = price;\n }\n\n //https://compound.finance/docs/prices\n function getPrice(address token) public view returns (uint256) {\n return assetPrices[token];\n }\n}\n" + }, + "contracts/oracles/mocks/MockPendlePtOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/IPendlePtOracle.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockPendlePtOracle is IPendlePtOracle, Ownable {\n mapping(address => mapping(uint32 => uint256)) public ptToAssetRate;\n mapping(address => mapping(uint32 => uint256)) public ptToSyRate;\n\n constructor() Ownable() {}\n\n function setPtToAssetRate(address market, uint32 duration, uint256 rate) external onlyOwner {\n ptToAssetRate[market][duration] = rate;\n }\n\n function setPtToSyRate(address market, uint32 duration, uint256 rate) external onlyOwner {\n ptToSyRate[market][duration] = rate;\n }\n\n function getPtToAssetRate(address market, uint32 duration) external view returns (uint256) {\n return ptToAssetRate[market][duration];\n }\n\n function getPtToSyRate(address market, uint32 duration) external view returns (uint256) {\n return ptToSyRate[market][duration];\n }\n\n function getOracleState(\n address /* market */,\n uint32 /* duration */\n )\n external\n pure\n returns (bool increaseCardinalityRequired, uint16 cardinalityRequired, bool oldestObservationSatisfied)\n {\n return (false, 0, true);\n }\n}\n" + }, + "contracts/oracles/mocks/MockSFrxEthFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/ISfrxEthFraxOracle.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockSfrxEthFraxOracle is ISfrxEthFraxOracle, Ownable {\n bool public isBadData;\n uint256 public priceLow;\n uint256 public priceHigh;\n\n constructor() Ownable() {}\n\n function setPrices(bool _isBadData, uint256 _priceLow, uint256 _priceHigh) external onlyOwner {\n isBadData = _isBadData;\n priceLow = _priceLow;\n priceHigh = _priceHigh;\n }\n\n function getPrices() external view override returns (bool, uint256, uint256) {\n return (isBadData, priceLow, priceHigh);\n }\n}\n" + }, + "contracts/oracles/OneJumpOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\n/**\n * @title OneJumpOracle\n * @author Venus\n * @notice This oracle fetches the price of an asset in through an intermediate asset\n */\ncontract OneJumpOracle is CorrelatedTokenOracle {\n /// @notice Address of the intermediate oracle\n OracleInterface public immutable INTERMEDIATE_ORACLE;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n address intermediateOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(intermediateOracle);\n INTERMEDIATE_ORACLE = OracleInterface(intermediateOracle);\n }\n\n /**\n * @notice Fetches the amount of the underlying token for 1 correlated token, using the intermediate oracle\n * @return amount The amount of the underlying token for 1 correlated token scaled by the underlying token decimals\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n uint256 underlyingDecimals = IERC20Metadata(UNDERLYING_TOKEN).decimals();\n uint256 correlatedDecimals = IERC20Metadata(CORRELATED_TOKEN).decimals();\n\n uint256 underlyingAmount = INTERMEDIATE_ORACLE.getPrice(CORRELATED_TOKEN);\n\n return (underlyingAmount * (10 ** correlatedDecimals)) / (10 ** (36 - underlyingDecimals));\n }\n}\n" + }, + "contracts/oracles/PendleOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IPendlePtOracle } from \"../interfaces/IPendlePtOracle.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\n/**\n * @title PendleOracle\n * @author Venus\n * @notice This oracle fetches the price of a pendle token\n * @dev As a base price the oracle uses either the price of the Pendle\n * market's asset (in this case PT_TO_ASSET rate should be used) or\n * the price of the Pendle market's interest bearing token (e.g. wstETH\n * for stETH; in this case PT_TO_SY rate should be used). Technically,\n * interest bearing token is different from standardized yield (SY) token,\n * but since SY is a wrapper around an interest bearing token, we can safely\n * assume the prices of the two are equal. This is not always true for asset\n * price though: using PT_TO_ASSET rate assumes that the yield token can\n * be seamlessly redeemed for the underlying asset. In reality, this might\n * not always be the case. For more details, see\n * https://docs.pendle.finance/Developers/Contracts/StandardizedYield\n */\ncontract PendleOracle is CorrelatedTokenOracle {\n struct ConstructorParams {\n /// @notice Pendle market\n address market;\n /// @notice Pendle oracle\n address ptOracle;\n /// @notice Either PT_TO_ASSET or PT_TO_SY\n RateKind rateKind;\n /// @notice Pendle PT token\n address ptToken;\n /// @notice Underlying token, can be either the market's asset or the interest bearing token\n address underlyingToken;\n /// @notice Resilient oracle to get the underlying token price from\n address resilientOracle;\n /// @notice TWAP duration to call Pendle oracle with\n uint32 twapDuration;\n /// @notice Annual growth rate of the underlying token\n uint256 annualGrowthRate;\n /// @notice Snapshot interval for the oracle\n uint256 snapshotInterval;\n /// @notice Initial exchange rate of the underlying token\n uint256 initialSnapshotMaxExchangeRate;\n /// @notice Initial timestamp of the underlying token\n uint256 initialSnapshotTimestamp;\n /// @notice Access control manager\n address accessControlManager;\n /// @notice Gap to add when updating the snapshot\n uint256 snapshotGap;\n }\n\n /// @notice Which asset to use as a base for the returned PT\n /// price. Can be either a standardized yield token (SY), in\n /// this case PT/SY price is returned, or the Pendle\n /// market's asset directly.\n enum RateKind {\n PT_TO_ASSET,\n PT_TO_SY\n }\n\n /// @notice Address of the PT oracle\n IPendlePtOracle public immutable PT_ORACLE;\n\n /// @notice Whether to use PT/SY (standardized yield token) rate\n /// or PT/market asset rate\n RateKind public immutable RATE_KIND;\n\n /// @notice Address of the market\n address public immutable MARKET;\n\n /// @notice Twap duration for the oracle\n uint32 public immutable TWAP_DURATION;\n\n /// @notice Decimals of the underlying token\n /// @dev We make an assumption that the underlying decimals will\n /// not change throughout the lifetime of the Pendle market\n uint8 public immutable UNDERLYING_DECIMALS;\n\n /// @notice Thrown if the duration is invalid\n error InvalidDuration();\n\n /**\n * @notice Constructor for the implementation contract.\n * @custom:error InvalidDuration Thrown if the duration is invalid\n */\n constructor(\n ConstructorParams memory params\n )\n CorrelatedTokenOracle(\n params.ptToken,\n params.underlyingToken,\n params.resilientOracle,\n params.annualGrowthRate,\n params.snapshotInterval,\n params.initialSnapshotMaxExchangeRate,\n params.initialSnapshotTimestamp,\n params.accessControlManager,\n params.snapshotGap\n )\n {\n ensureNonzeroAddress(params.market);\n ensureNonzeroAddress(params.ptOracle);\n ensureNonzeroValue(params.twapDuration);\n\n MARKET = params.market;\n PT_ORACLE = IPendlePtOracle(params.ptOracle);\n RATE_KIND = params.rateKind;\n TWAP_DURATION = params.twapDuration;\n UNDERLYING_DECIMALS = IERC20Metadata(UNDERLYING_TOKEN).decimals();\n\n (bool increaseCardinalityRequired, , bool oldestObservationSatisfied) = PT_ORACLE.getOracleState(\n MARKET,\n TWAP_DURATION\n );\n if (increaseCardinalityRequired || !oldestObservationSatisfied) {\n revert InvalidDuration();\n }\n }\n\n /// @notice Fetches the amount of underlying token for 1 PT\n /// @return amount The amount of underlying token (either the market's asset\n /// or the yield token) for 1 PT, adjusted for decimals such that the result\n /// has the same precision as the underlying token\n function getUnderlyingAmount() public view override returns (uint256) {\n uint256 rate;\n if (RATE_KIND == RateKind.PT_TO_SY) {\n rate = PT_ORACLE.getPtToSyRate(MARKET, TWAP_DURATION);\n } else {\n rate = PT_ORACLE.getPtToAssetRate(MARKET, TWAP_DURATION);\n }\n return ((10 ** UNDERLYING_DECIMALS) * rate) / 1e18;\n }\n}\n" + }, + "contracts/oracles/SequencerChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.25;\n\nimport { ChainlinkOracle } from \"./ChainlinkOracle.sol\";\nimport { AggregatorV3Interface } from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\n\n/**\n @title Sequencer Chain Link Oracle\n @notice Oracle to fetch price using chainlink oracles on L2s with sequencer\n*/\ncontract SequencerChainlinkOracle is ChainlinkOracle {\n /// @notice L2 Sequencer feed\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n AggregatorV3Interface public immutable sequencer;\n\n /// @notice L2 Sequencer grace period\n uint256 public constant GRACE_PERIOD_TIME = 3600;\n\n /**\n @notice Contract constructor\n @param _sequencer L2 sequencer\n @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(AggregatorV3Interface _sequencer) ChainlinkOracle() {\n require(address(_sequencer) != address(0), \"zero address\");\n\n sequencer = _sequencer;\n }\n\n /// @inheritdoc ChainlinkOracle\n function getPrice(address asset) public view override returns (uint) {\n if (!isSequencerActive()) revert(\"L2 sequencer unavailable\");\n return super.getPrice(asset);\n }\n\n function isSequencerActive() internal view returns (bool) {\n // answer from oracle is a variable with a value of either 1 or 0\n // 0: The sequencer is up\n // 1: The sequencer is down\n // startedAt: This timestamp indicates when the sequencer changed status\n (, int256 answer, uint256 startedAt, , ) = sequencer.latestRoundData();\n if (block.timestamp - startedAt <= GRACE_PERIOD_TIME || answer == 1) return false;\n return true;\n }\n}\n" + }, + "contracts/oracles/SFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISFrax } from \"../interfaces/ISFrax.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title SFraxOracle\n * @author Venus\n * @notice This oracle fetches the price of sFrax\n */\ncontract SFraxOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address sFrax,\n address frax,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n sFrax,\n frax,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of FRAX for 1 sFrax\n * @return amount The amount of FRAX for sFrax\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ISFrax(CORRELATED_TOKEN).convertToAssets(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/SFrxETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISfrxEthFraxOracle } from \"../interfaces/ISfrxEthFraxOracle.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title SFrxETHOracle\n * @author Venus\n * @notice This oracle fetches the price of sfrxETH\n */\ncontract SFrxETHOracle is AccessControlledV8, OracleInterface {\n /// @notice Address of SfrxEthFraxOracle\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ISfrxEthFraxOracle public immutable SFRXETH_FRAX_ORACLE;\n\n /// @notice Address of sfrxETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable SFRXETH;\n\n /// @notice Maximum allowed price difference\n uint256 public maxAllowedPriceDifference;\n\n /// @notice Emits when the maximum allowed price difference is updated\n event MaxAllowedPriceDifferenceUpdated(uint256 oldMaxAllowedPriceDifference, uint256 newMaxAllowedPriceDifference);\n\n /// @notice Thrown if the price data is invalid\n error BadPriceData();\n\n /// @notice Thrown if the price difference exceeds the allowed limit\n error PriceDifferenceExceeded();\n\n /// @notice Thrown if the token address is invalid\n error InvalidTokenAddress();\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n /// @custom:error ZeroAddressNotAllowed is thrown when `_sfrxEthFraxOracle` or `_sfrxETH` are the zero address\n constructor(address _sfrxEthFraxOracle, address _sfrxETH) {\n ensureNonzeroAddress(_sfrxEthFraxOracle);\n ensureNonzeroAddress(_sfrxETH);\n\n SFRXETH_FRAX_ORACLE = ISfrxEthFraxOracle(_sfrxEthFraxOracle);\n SFRXETH = _sfrxETH;\n\n _disableInitializers();\n }\n\n /**\n * @notice Sets the contracts required to fetch prices\n * @param _acm Address of the access control manager contract\n * @param _maxAllowedPriceDifference Maximum allowed price difference\n * @custom:error ZeroValueNotAllowed is thrown if `_maxAllowedPriceDifference` is zero\n */\n function initialize(address _acm, uint256 _maxAllowedPriceDifference) external initializer {\n ensureNonzeroValue(_maxAllowedPriceDifference);\n\n __AccessControlled_init(_acm);\n maxAllowedPriceDifference = _maxAllowedPriceDifference;\n }\n\n /**\n * @notice Sets the maximum allowed price difference\n * @param _maxAllowedPriceDifference Maximum allowed price difference\n * @custom:error ZeroValueNotAllowed is thrown if `_maxAllowedPriceDifference` is zero\n */\n function setMaxAllowedPriceDifference(uint256 _maxAllowedPriceDifference) external {\n _checkAccessAllowed(\"setMaxAllowedPriceDifference(uint256)\");\n ensureNonzeroValue(_maxAllowedPriceDifference);\n\n emit MaxAllowedPriceDifferenceUpdated(maxAllowedPriceDifference, _maxAllowedPriceDifference);\n maxAllowedPriceDifference = _maxAllowedPriceDifference;\n }\n\n /**\n * @notice Fetches the USD price of sfrxETH\n * @param asset Address of the sfrxETH token\n * @return price The price scaled by 1e18\n * @custom:error InvalidTokenAddress is thrown when the `asset` is not the sfrxETH token (`SFRXETH`)\n * @custom:error BadPriceData is thrown if the `SFRXETH_FRAX_ORACLE` oracle informs it has bad data\n * @custom:error ZeroValueNotAllowed is thrown if the prices (low or high, in USD) are zero\n * @custom:error PriceDifferenceExceeded is thrown if priceHigh/priceLow is greater than `maxAllowedPriceDifference`\n */\n function getPrice(address asset) external view returns (uint256) {\n if (asset != SFRXETH) revert InvalidTokenAddress();\n\n (bool isBadData, uint256 priceLow, uint256 priceHigh) = SFRXETH_FRAX_ORACLE.getPrices();\n\n if (isBadData) revert BadPriceData();\n\n // calculate price in USD\n uint256 priceHighInUSD = (EXP_SCALE ** 2) / priceLow;\n uint256 priceLowInUSD = (EXP_SCALE ** 2) / priceHigh;\n\n ensureNonzeroValue(priceHighInUSD);\n ensureNonzeroValue(priceLowInUSD);\n\n // validate price difference\n uint256 difference = (priceHighInUSD * EXP_SCALE) / priceLowInUSD;\n if (difference > maxAllowedPriceDifference) revert PriceDifferenceExceeded();\n\n // calculate and return average price\n return (priceHighInUSD + priceLowInUSD) / 2;\n }\n}\n" + }, + "contracts/oracles/SlisBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISynclubStakeManager } from \"../interfaces/ISynclubStakeManager.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title SlisBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of slisBNB asset\n */\ncontract SlisBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakeManager\n ISynclubStakeManager public immutable STAKE_MANAGER;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakeManager,\n address slisBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n slisBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakeManager);\n STAKE_MANAGER = ISynclubStakeManager(stakeManager);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 slisBNB\n * @return amount The amount of BNB for slisBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STAKE_MANAGER.convertSnBnbToBnb(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/StableUsdtPriceFeed.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ResilientOracleInterface } from \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title StableUsdtPriceFeed\n * @dev This contract is used to get the price of USDT from a Resilient Oracle\n * and bounds the price to a certain range.\n */\ncontract StableUsdtPriceFeed {\n ResilientOracleInterface public resilientOracle;\n\n address public constant USDT_TOKEN_ADDR = 0x55d398326f99059fF775485246999027B3197955;\n uint256 public constant UPPER_BOUND = 1020000000000000000; // 1.02 USD\n uint256 public constant LOWER_BOUND = 980000000000000000; // 0.98 USD\n\n constructor(address _resilientOracle) {\n require(_resilientOracle != address(0), \"Zero address provided\");\n resilientOracle = ResilientOracleInterface(_resilientOracle);\n }\n\n function latestAnswer() external view returns (int256 answer) {\n // get price\n uint256 price = getPrice();\n // cast price to int256\n answer = int256(price);\n }\n\n function latestRoundData()\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n // get price\n uint256 _answer = getPrice();\n // mock timestamp to latest block timestamp\n uint256 timestamp = block.timestamp;\n // mock roundId to timestamp\n roundId = uint80(timestamp);\n return (roundId, int256(_answer), timestamp, timestamp, roundId);\n }\n\n function decimals() external pure returns (uint8) {\n return 18;\n }\n\n function description() external pure returns (string memory) {\n return \"Stabilized USDT Price Feed\";\n }\n\n function version() external pure returns (uint256) {\n return 1;\n }\n\n /**\n * @dev Get the price from the Resilient Oracle, and bound it to the range\n * @return price The price of USDT in 18 decimals\n */\n function getPrice() private view returns (uint256 price) {\n // get USDT price (18 decimals)\n price = resilientOracle.getPrice(USDT_TOKEN_ADDR);\n price = price < LOWER_BOUND ? LOWER_BOUND : (price > UPPER_BOUND ? UPPER_BOUND : price);\n }\n}\n" + }, + "contracts/oracles/StkBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IPStakePool } from \"../interfaces/IPStakePool.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title StkBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of stkBNB asset\n */\ncontract StkBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakePool\n IPStakePool public immutable STAKE_POOL;\n\n /// @notice Thrown if the pool token supply is zero\n error PoolTokenSupplyIsZero();\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakePool,\n address stkBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n stkBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakePool);\n STAKE_POOL = IPStakePool(stakePool);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 stkBNB\n * @return price The amount of BNB for stkBNB\n * @custom:error PoolTokenSupplyIsZero error is thrown if the pool token supply is zero\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n IPStakePool.Data memory exchangeRateData = STAKE_POOL.exchangeRate();\n\n if (exchangeRateData.poolTokenSupply == 0) {\n revert PoolTokenSupplyIsZero();\n }\n\n return (exchangeRateData.totalWei * EXP_SCALE) / exchangeRateData.poolTokenSupply;\n }\n}\n" + }, + "contracts/oracles/WBETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IWBETH } from \"../interfaces/IWBETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title WBETHOracle\n * @author Venus\n * @notice This oracle fetches the price of wBETH asset\n */\ncontract WBETHOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address wbeth,\n address eth,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n wbeth,\n eth,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of ETH for 1 wBETH\n * @return amount The amount of ETH for wBETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IWBETH(CORRELATED_TOKEN).exchangeRate();\n }\n}\n" + }, + "contracts/oracles/WeETHAccountantOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IAccountant } from \"../interfaces/IAccountant.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WeETHAccountantOracle\n * @author Venus\n * @notice This oracle fetches the price of Ether.fi tokens based on an `Accountant` contract (i.e. weETHs and weETHk)\n */\ncontract WeETHAccountantOracle is CorrelatedTokenOracle {\n /// @notice Address of Accountant\n IAccountant public immutable ACCOUNTANT;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address accountant,\n address weethLRT,\n address weth,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n weethLRT,\n weth,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(accountant);\n ACCOUNTANT = IAccountant(accountant);\n }\n\n /**\n * @notice Gets the WETH for 1 weETH LRT\n * @return amount Amount of WETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ACCOUNTANT.getRateSafe();\n }\n}\n" + }, + "contracts/oracles/WeETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IEtherFiLiquidityPool } from \"../interfaces/IEtherFiLiquidityPool.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WeETHOracle\n * @author Venus\n * @notice This oracle fetches the price of weETH\n */\ncontract WeETHOracle is CorrelatedTokenOracle {\n /// @notice Address of Liqiudity pool\n IEtherFiLiquidityPool public immutable LIQUIDITY_POOL;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address liquidityPool,\n address weETH,\n address eETH,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n weETH,\n eETH,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(liquidityPool);\n LIQUIDITY_POOL = IEtherFiLiquidityPool(liquidityPool);\n }\n\n /**\n * @notice Gets the eETH for 1 weETH\n * @return amount Amount of eETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return LIQUIDITY_POOL.amountForShare(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/WstETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\nimport { IStETH } from \"../interfaces/IStETH.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title WstETHOracle\n * @author Venus\n * @notice Depending on the equivalence flag price is either based on assumption that 1 stETH = 1 ETH\n * or the price of stETH/USD (secondary market price) is obtained from the oracle.\n */\ncontract WstETHOracle is OracleInterface {\n /// @notice A flag assuming 1:1 price equivalence between stETH/ETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n bool public immutable ASSUME_STETH_ETH_EQUIVALENCE;\n\n /// @notice Address of stETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IStETH public immutable STETH;\n\n /// @notice Address of wstETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable WSTETH_ADDRESS;\n\n /// @notice Address of WETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable WETH_ADDRESS;\n\n /// @notice Address of Resilient Oracle\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n OracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(\n address wstETHAddress,\n address wETHAddress,\n address stETHAddress,\n address resilientOracleAddress,\n bool assumeEquivalence\n ) {\n ensureNonzeroAddress(wstETHAddress);\n ensureNonzeroAddress(wETHAddress);\n ensureNonzeroAddress(stETHAddress);\n ensureNonzeroAddress(resilientOracleAddress);\n WSTETH_ADDRESS = wstETHAddress;\n WETH_ADDRESS = wETHAddress;\n STETH = IStETH(stETHAddress);\n RESILIENT_ORACLE = OracleInterface(resilientOracleAddress);\n ASSUME_STETH_ETH_EQUIVALENCE = assumeEquivalence;\n }\n\n /**\n * @notice Gets the USD price of wstETH asset\n * @dev Depending on the equivalence flag price is either based on assumption that 1 stETH = 1 ETH\n * or the price of stETH/USD (secondary market price) is obtained from the oracle\n * @param asset Address of wstETH\n * @return wstETH Price in USD scaled by 1e18\n */\n function getPrice(address asset) public view returns (uint256) {\n if (asset != WSTETH_ADDRESS) revert(\"wrong wstETH address\");\n\n // get stETH amount for 1 wstETH scaled by 1e18\n uint256 stETHAmount = STETH.getPooledEthByShares(1 ether);\n\n // price is scaled 1e18 (oracle returns 36 - asset decimal scale)\n uint256 stETHUSDPrice = RESILIENT_ORACLE.getPrice(ASSUME_STETH_ETH_EQUIVALENCE ? WETH_ADDRESS : address(STETH));\n\n // stETHAmount (for 1 wstETH) * stETHUSDPrice / 1e18\n return (stETHAmount * stETHUSDPrice) / EXP_SCALE;\n }\n}\n" + }, + "contracts/oracles/WstETHOracleV2.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IStETH } from \"../interfaces/IStETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WstETHOracleV2\n * @author Venus\n * @notice This oracle fetches the price of wstETH\n */\ncontract WstETHOracleV2 is CorrelatedTokenOracle {\n /// @notice Address of stETH\n IStETH public immutable STETH;\n\n /// @notice Constructor for the implementation contract.\n /// @dev The underlyingToken must be correlated so that 1 underlyingToken is equal to 1 stETH, because\n /// getUnderlyingAmount() implicitly assumes that\n constructor(\n address stETH,\n address wstETH,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n wstETH,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stETH);\n STETH = IStETH(stETH);\n }\n\n /**\n * @notice Gets the amount of underlyingToken for 1 wstETH, assuming that 1 underlyingToken is equivalent to 1 stETH\n * @return amount Amount of underlyingToken\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STETH.getPooledEthByShares(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/ZkETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IZkETH } from \"../interfaces/IZkETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title ZkETHOracle\n * @author Venus\n * @notice This oracle fetches the price of zkETH\n */\ncontract ZkETHOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address zkETH,\n address rzkETH,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n zkETH,\n rzkETH,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Gets the amount of rzkETH for 1 zkETH\n * @return amount Amount of rzkETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IZkETH(CORRELATED_TOKEN).LSTPerToken();\n }\n}\n" + }, + "contracts/ReferenceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2025 Venus\npragma solidity 0.8.25;\n\nimport { Ownable2StepUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { ResilientOracleInterface, OracleInterface } from \"./interfaces/OracleInterface.sol\";\n\n/**\n * @title ReferenceOracle\n * @author Venus\n * @notice Reference oracle is the oracle that is not used for production but required for\n * price monitoring. This oracle contains some extra configurations for assets required to\n * compute reference prices of their derivative assets (OneJump, ERC4626, Pendle, etc.)\n */\ncontract ReferenceOracle is Ownable2StepUpgradeable, OracleInterface {\n struct ExternalPrice {\n /// @notice asset address\n address asset;\n /// @notice price of the asset from an external source\n uint256 price;\n }\n\n /// @notice Slot to temporarily store price information from external sources\n /// like CMC/Coingecko, useful to compute prices of derivative assets based on\n /// prices of the base assets with no on chain price information\n bytes32 public constant PRICES_SLOT = keccak256(abi.encode(\"venus-protocol/oracle/ReferenceOracle/prices\"));\n\n /// @notice Resilient oracle address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Oracle configuration for assets\n mapping(address => OracleInterface) public oracles;\n\n /// @notice Event emitted when an oracle is set\n event OracleConfigured(address indexed asset, address indexed oracle);\n\n /**\n * @notice Constructor for the implementation contract. Sets immutable variables.\n * @param resilientOracle Resilient oracle address\n * @custom:error ZeroAddressNotAllowed is thrown if resilient oracle address is null\n * @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(ResilientOracleInterface resilientOracle) {\n ensureNonzeroAddress(address(resilientOracle));\n RESILIENT_ORACLE = resilientOracle;\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin\n */\n function initialize() external initializer {\n __Ownable2Step_init();\n }\n\n /**\n * @notice Sets an oracle to use for a specific asset\n * @dev The production resilientOracle will be used if zero address is passed\n * @param asset Asset address\n * @param oracle Oracle address\n * @custom:access Only owner\n * @custom:error ZeroAddressNotAllowed is thrown if asset address is null\n * @custom:event Emits OracleConfigured event\n */\n function setOracle(address asset, OracleInterface oracle) external onlyOwner {\n ensureNonzeroAddress(asset);\n oracles[asset] = OracleInterface(oracle);\n emit OracleConfigured(asset, address(oracle));\n }\n\n /**\n * @notice Gets price of the asset assuming other assets have the defined price\n * @param asset asset address\n * @param externalPrices an array of prices for other assets\n * @return USD price in scaled decimal places\n */\n function getPriceAssuming(address asset, ExternalPrice[] memory externalPrices) external returns (uint256) {\n uint256 externalPricesCount = externalPrices.length;\n for (uint256 i = 0; i < externalPricesCount; ++i) {\n _storeExternalPrice(externalPrices[i].asset, externalPrices[i].price);\n }\n return _getPrice(asset);\n }\n\n /**\n * @notice Gets price of the asset\n * @param asset asset address\n * @return USD price in scaled decimal places\n */\n function getPrice(address asset) external view override returns (uint256) {\n return _getPrice(asset);\n }\n\n function _storeExternalPrice(address asset, uint256 price) internal {\n bytes32 slot = keccak256(abi.encode(PRICES_SLOT, asset));\n // solhint-disable-next-line no-inline-assembly\n assembly (\"memory-safe\") {\n tstore(slot, price)\n }\n }\n\n function _getPrice(address asset) internal view returns (uint256) {\n uint256 externalPrice = _loadExternalPrice(asset);\n if (externalPrice != 0) {\n return externalPrice;\n }\n OracleInterface oracle = oracles[asset];\n if (oracle != OracleInterface(address(0))) {\n return oracle.getPrice(asset);\n }\n return RESILIENT_ORACLE.getPrice(asset);\n }\n\n function _loadExternalPrice(address asset) internal view returns (uint256 value) {\n bytes32 slot = keccak256(abi.encode(PRICES_SLOT, asset));\n // solhint-disable-next-line no-inline-assembly\n assembly (\"memory-safe\") {\n value := tload(slot)\n }\n }\n}\n" + }, + "contracts/ResilientOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity 0.8.25;\n\nimport { PausableUpgradeable } from \"@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol\";\nimport { VBep20Interface } from \"./interfaces/VBep20Interface.sol\";\nimport { OracleInterface, ResilientOracleInterface, BoundValidatorInterface } from \"./interfaces/OracleInterface.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { ICappedOracle } from \"./interfaces/ICappedOracle.sol\";\nimport { Transient } from \"./lib/Transient.sol\";\n\n/**\n * @title ResilientOracle\n * @author Venus\n * @notice The Resilient Oracle is the main contract that the protocol uses to fetch prices of assets.\n *\n * DeFi protocols are vulnerable to price oracle failures including oracle manipulation and incorrectly\n * reported prices. If only one oracle is used, this creates a single point of failure and opens a vector\n * for attacking the protocol.\n *\n * The Resilient Oracle uses multiple sources and fallback mechanisms to provide accurate prices and protect\n * the protocol from oracle attacks.\n *\n * For every market (vToken) we configure the main, pivot and fallback oracles. The oracles are configured per\n * vToken's underlying asset address. The main oracle oracle is the most trustworthy price source, the pivot\n * oracle is used as a loose sanity checker and the fallback oracle is used as a backup price source.\n *\n * To validate prices returned from two oracles, we use an upper and lower bound ratio that is set for every\n * market. The upper bound ratio represents the deviation between reported price (the price that’s being\n * validated) and the anchor price (the price we are validating against) above which the reported price will\n * be invalidated. The lower bound ratio presents the deviation between reported price and anchor price below\n * which the reported price will be invalidated. So for oracle price to be considered valid the below statement\n * should be true:\n\n```\nanchorRatio = anchorPrice/reporterPrice\nisValid = anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio\n```\n\n * In most cases, Chainlink is used as the main oracle, other oracles are used as the pivot oracle depending\n * on which supports the given market and Binance oracle is used as the fallback oracle.\n *\n * For a fetched price to be valid it must be positive and not stagnant. If the price is invalid then we consider the\n * oracle to be stagnant and treat it like it's disabled.\n */\ncontract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOracleInterface {\n /**\n * @dev Oracle roles:\n * **main**: The most trustworthy price source\n * **pivot**: Price oracle used as a loose sanity checker\n * **fallback**: The backup source when main oracle price is invalidated\n */\n enum OracleRole {\n MAIN,\n PIVOT,\n FALLBACK\n }\n\n struct TokenConfig {\n /// @notice asset address\n address asset;\n /// @notice `oracles` stores the oracles based on their role in the following order:\n /// [main, pivot, fallback],\n /// It can be indexed with the corresponding enum OracleRole value\n address[3] oracles;\n /// @notice `enableFlagsForOracles` stores the enabled state\n /// for each oracle in the same order as `oracles`\n bool[3] enableFlagsForOracles;\n /// @notice `cachingEnabled` is a flag that indicates whether the asset price should be cached\n bool cachingEnabled;\n }\n\n uint256 public constant INVALID_PRICE = 0;\n\n /// @notice Native market address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable nativeMarket;\n\n /// @notice VAI address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vai;\n\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\n /// and can serve as any underlying asset of a market that supports native tokens\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Slot to cache the asset's price, used for transient storage\n /// custom:storage-location erc7201:venus-protocol/oracle/ResilientOracle/cache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/ResilientOracle/cache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant CACHE_SLOT = 0x4e99ec55972332f5e0ef9c6623192c0401b609161bffae64d9ccdd7ad6cc7800;\n\n /// @notice Bound validator contract address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n BoundValidatorInterface public immutable boundValidator;\n\n mapping(address => TokenConfig) private tokenConfigs;\n\n event TokenConfigAdded(\n address indexed asset,\n address indexed mainOracle,\n address indexed pivotOracle,\n address fallbackOracle\n );\n\n /// Event emitted when an oracle is set\n event OracleSet(address indexed asset, address indexed oracle, uint256 indexed role);\n\n /// Event emitted when an oracle is enabled or disabled\n event OracleEnabled(address indexed asset, uint256 indexed role, bool indexed enable);\n\n /// Event emitted when an asset cachingEnabled flag is set\n event CachedEnabled(address indexed asset, bool indexed enabled);\n\n /**\n * @notice Checks whether an address is null or not\n */\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /**\n * @notice Checks whether token config exists by checking whether asset is null address\n * @dev address can't be null, so it's suitable to be used to check the validity of the config\n * @param asset asset address\n */\n modifier checkTokenConfigExistence(address asset) {\n if (tokenConfigs[asset].asset == address(0)) revert(\"token config must exist\");\n _;\n }\n\n /// @notice Constructor for the implementation contract. Sets immutable variables.\n /// @dev nativeMarketAddress can be address(0) if on the chain we do not support native market\n /// (e.g vETH on ethereum would not be supported, only vWETH)\n /// @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\n /// @param vaiAddress The address of the VAI token (if there is VAI on the deployed chain).\n /// Set to address(0) of VAI is not existent.\n /// @param _boundValidator Address of the bound validator contract\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(\n address nativeMarketAddress,\n address vaiAddress,\n BoundValidatorInterface _boundValidator\n ) notNullAddress(address(_boundValidator)) {\n nativeMarket = nativeMarketAddress;\n vai = vaiAddress;\n boundValidator = _boundValidator;\n\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin and sets the BoundValidator contract address\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n __Pausable_init();\n }\n\n /**\n * @notice Pauses oracle\n * @custom:access Only Governance\n */\n function pause() external {\n _checkAccessAllowed(\"pause()\");\n _pause();\n }\n\n /**\n * @notice Unpauses oracle\n * @custom:access Only Governance\n */\n function unpause() external {\n _checkAccessAllowed(\"unpause()\");\n _unpause();\n }\n\n /**\n * @notice Batch sets token configs\n * @param tokenConfigs_ Token config array\n * @custom:access Only Governance\n * @custom:error Throws a length error if the length of the token configs array is 0\n */\n function setTokenConfigs(TokenConfig[] memory tokenConfigs_) external {\n if (tokenConfigs_.length == 0) revert(\"length can't be 0\");\n uint256 numTokenConfigs = tokenConfigs_.length;\n for (uint256 i; i < numTokenConfigs; ++i) {\n setTokenConfig(tokenConfigs_[i]);\n }\n }\n\n /**\n * @notice Sets oracle for a given asset and role.\n * @dev Supplied asset **must** exist and main oracle may not be null\n * @param asset Asset address\n * @param oracle Oracle address\n * @param role Oracle role\n * @custom:access Only Governance\n * @custom:error Null address error if main-role oracle address is null\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error TokenConfigExistance error is thrown if token config is not set\n * @custom:event Emits OracleSet event with asset address, oracle address and role of the oracle for the asset\n */\n function setOracle(\n address asset,\n address oracle,\n OracleRole role\n ) external notNullAddress(asset) checkTokenConfigExistence(asset) {\n _checkAccessAllowed(\"setOracle(address,address,uint8)\");\n if (oracle == address(0) && role == OracleRole.MAIN) revert(\"can't set zero address to main oracle\");\n tokenConfigs[asset].oracles[uint256(role)] = oracle;\n emit OracleSet(asset, oracle, uint256(role));\n }\n\n /**\n * @notice Enables/ disables oracle for the input asset. Token config for the input asset **must** exist\n * @dev Configuration for the asset **must** already exist and the asset cannot be 0 address\n * @param asset Asset address\n * @param role Oracle role\n * @param enable Enabled boolean of the oracle\n * @custom:access Only Governance\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error TokenConfigExistance error is thrown if token config is not set\n * @custom:event Emits OracleEnabled event with asset address, role of the oracle and enabled flag\n */\n function enableOracle(\n address asset,\n OracleRole role,\n bool enable\n ) external notNullAddress(asset) checkTokenConfigExistence(asset) {\n _checkAccessAllowed(\"enableOracle(address,uint8,bool)\");\n tokenConfigs[asset].enableFlagsForOracles[uint256(role)] = enable;\n emit OracleEnabled(asset, uint256(role), enable);\n }\n\n /**\n * @notice Updates the capped main oracle snapshot.\n * @dev This function should always be called before calling getUnderlyingPrice\n * @param vToken vToken address\n */\n function updatePrice(address vToken) external override {\n address asset = _getUnderlyingAsset(vToken);\n _updateAssetPrice(asset);\n }\n\n /**\n * @notice Updates the capped main oracle snapshot.\n * @dev This function should always be called before calling getPrice\n * @param asset asset address\n */\n function updateAssetPrice(address asset) external {\n _updateAssetPrice(asset);\n }\n\n /**\n * @dev Gets token config by asset address\n * @param asset asset address\n * @return tokenConfig Config for the asset\n */\n function getTokenConfig(address asset) external view returns (TokenConfig memory) {\n return tokenConfigs[asset];\n }\n\n /**\n * @notice Gets price of the underlying asset for a given vToken. Validation flow:\n * - Check if the oracle is paused globally\n * - Validate price from main oracle against pivot oracle\n * - Validate price from fallback oracle against pivot oracle if the first validation failed\n * - Validate price from main oracle against fallback oracle if the second validation failed\n * In the case that the pivot oracle is not available but main price is available and validation is successful,\n * main oracle price is returned.\n * @param vToken vToken address\n * @return price USD price in scaled decimal places.\n * @custom:error Paused error is thrown when resilent oracle is paused\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function getUnderlyingPrice(address vToken) external view override returns (uint256) {\n if (paused()) revert(\"resilient oracle is paused\");\n\n address asset = _getUnderlyingAsset(vToken);\n return _getPrice(asset);\n }\n\n /**\n * @notice Gets price of the asset\n * @param asset asset address\n * @return price USD price in scaled decimal places.\n * @custom:error Paused error is thrown when resilent oracle is paused\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function getPrice(address asset) external view override returns (uint256) {\n if (paused()) revert(\"resilient oracle is paused\");\n return _getPrice(asset);\n }\n\n /**\n * @notice Sets/resets single token configs.\n * @dev main oracle **must not** be a null address\n * @param tokenConfig Token config struct\n * @custom:access Only Governance\n * @custom:error NotNullAddress is thrown if asset address is null\n * @custom:error NotNullAddress is thrown if main-role oracle address for asset is null\n * @custom:event Emits TokenConfigAdded event when the asset config is set successfully by the authorized account\n * @custom:event Emits CachedEnabled event when the asset cachingEnabled flag is set successfully\n */\n function setTokenConfig(\n TokenConfig memory tokenConfig\n ) public notNullAddress(tokenConfig.asset) notNullAddress(tokenConfig.oracles[uint256(OracleRole.MAIN)]) {\n _checkAccessAllowed(\"setTokenConfig(TokenConfig)\");\n\n tokenConfigs[tokenConfig.asset] = tokenConfig;\n emit TokenConfigAdded(\n tokenConfig.asset,\n tokenConfig.oracles[uint256(OracleRole.MAIN)],\n tokenConfig.oracles[uint256(OracleRole.PIVOT)],\n tokenConfig.oracles[uint256(OracleRole.FALLBACK)]\n );\n emit CachedEnabled(tokenConfig.asset, tokenConfig.cachingEnabled);\n }\n\n /**\n * @notice Gets oracle and enabled status by asset address\n * @param asset asset address\n * @param role Oracle role\n * @return oracle Oracle address based on role\n * @return enabled Enabled flag of the oracle based on token config\n */\n function getOracle(address asset, OracleRole role) public view returns (address oracle, bool enabled) {\n oracle = tokenConfigs[asset].oracles[uint256(role)];\n enabled = tokenConfigs[asset].enableFlagsForOracles[uint256(role)];\n }\n\n /**\n * @notice Updates the capped oracle snapshot.\n * @dev Cache the asset price and return if already cached\n * @param asset asset address\n */\n function _updateAssetPrice(address asset) internal {\n if (Transient.readCachedPrice(CACHE_SLOT, asset) != 0) {\n return;\n }\n\n (address mainOracle, bool mainOracleEnabled) = getOracle(asset, OracleRole.MAIN);\n if (mainOracle != address(0) && mainOracleEnabled) {\n // if main oracle is not CorrelatedTokenOracle it will revert so we need to catch the revert\n try ICappedOracle(mainOracle).updateSnapshot() {} catch {}\n }\n\n if (_isCacheEnabled(asset)) {\n uint256 price = _getPrice(asset);\n Transient.cachePrice(CACHE_SLOT, asset, price);\n }\n }\n\n /**\n * @notice Gets price for the provided asset\n * @param asset asset address\n * @return price USD price in scaled decimal places.\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function _getPrice(address asset) internal view returns (uint256) {\n uint256 pivotPrice = INVALID_PRICE;\n uint256 price;\n\n price = Transient.readCachedPrice(CACHE_SLOT, asset);\n if (price != 0) {\n return price;\n }\n\n // Get pivot oracle price, Invalid price if not available or error\n (address pivotOracle, bool pivotOracleEnabled) = getOracle(asset, OracleRole.PIVOT);\n if (pivotOracleEnabled && pivotOracle != address(0)) {\n try OracleInterface(pivotOracle).getPrice(asset) returns (uint256 pricePivot) {\n pivotPrice = pricePivot;\n } catch {}\n }\n\n // Compare main price and pivot price, return main price and if validation was successful\n // note: In case pivot oracle is not available but main price is available and\n // validation is successful, the main oracle price is returned.\n (uint256 mainPrice, bool validatedPivotMain) = _getMainOraclePrice(\n asset,\n pivotPrice,\n pivotOracleEnabled && pivotOracle != address(0)\n );\n if (mainPrice != INVALID_PRICE && validatedPivotMain) return mainPrice;\n\n // Compare fallback and pivot if main oracle comparision fails with pivot\n // Return fallback price when fallback price is validated successfully with pivot oracle\n (uint256 fallbackPrice, bool validatedPivotFallback) = _getFallbackOraclePrice(asset, pivotPrice);\n if (fallbackPrice != INVALID_PRICE && validatedPivotFallback) return fallbackPrice;\n\n // Lastly compare main price and fallback price\n if (\n mainPrice != INVALID_PRICE &&\n fallbackPrice != INVALID_PRICE &&\n boundValidator.validatePriceWithAnchorPrice(asset, mainPrice, fallbackPrice)\n ) {\n return mainPrice;\n }\n\n revert(\"invalid resilient oracle price\");\n }\n\n /**\n * @notice Gets a price for the provided asset\n * @dev This function won't revert when price is 0, because the fallback oracle may still be\n * able to fetch a correct price\n * @param asset asset address\n * @param pivotPrice Pivot oracle price\n * @param pivotEnabled If pivot oracle is not empty and enabled\n * @return price USD price in scaled decimals\n * e.g. asset decimals is 8 then price is returned as 10**18 * 10**(18-8) = 10**28 decimals\n * @return pivotValidated Boolean representing if the validation of main oracle price\n * and pivot oracle price were successful\n * @custom:error Invalid price error is thrown if main oracle fails to fetch price of the asset\n * @custom:error Invalid price error is thrown if main oracle is not enabled or main oracle\n * address is null\n */\n function _getMainOraclePrice(\n address asset,\n uint256 pivotPrice,\n bool pivotEnabled\n ) internal view returns (uint256, bool) {\n (address mainOracle, bool mainOracleEnabled) = getOracle(asset, OracleRole.MAIN);\n if (mainOracleEnabled && mainOracle != address(0)) {\n try OracleInterface(mainOracle).getPrice(asset) returns (uint256 mainOraclePrice) {\n if (!pivotEnabled) {\n return (mainOraclePrice, true);\n }\n if (pivotPrice == INVALID_PRICE) {\n return (mainOraclePrice, false);\n }\n return (\n mainOraclePrice,\n boundValidator.validatePriceWithAnchorPrice(asset, mainOraclePrice, pivotPrice)\n );\n } catch {\n return (INVALID_PRICE, false);\n }\n }\n\n return (INVALID_PRICE, false);\n }\n\n /**\n * @dev This function won't revert when the price is 0 because getPrice checks if price is > 0\n * @param asset asset address\n * @return price USD price in 18 decimals\n * @return pivotValidated Boolean representing if the validation of fallback oracle price\n * and pivot oracle price were successfully\n * @custom:error Invalid price error is thrown if fallback oracle fails to fetch price of the asset\n * @custom:error Invalid price error is thrown if fallback oracle is not enabled or fallback oracle\n * address is null\n */\n function _getFallbackOraclePrice(address asset, uint256 pivotPrice) private view returns (uint256, bool) {\n (address fallbackOracle, bool fallbackEnabled) = getOracle(asset, OracleRole.FALLBACK);\n if (fallbackEnabled && fallbackOracle != address(0)) {\n try OracleInterface(fallbackOracle).getPrice(asset) returns (uint256 fallbackOraclePrice) {\n if (pivotPrice == INVALID_PRICE) {\n return (fallbackOraclePrice, false);\n }\n return (\n fallbackOraclePrice,\n boundValidator.validatePriceWithAnchorPrice(asset, fallbackOraclePrice, pivotPrice)\n );\n } catch {\n return (INVALID_PRICE, false);\n }\n }\n\n return (INVALID_PRICE, false);\n }\n\n /**\n * @dev This function returns the underlying asset of a vToken\n * @param vToken vToken address\n * @return asset underlying asset address\n */\n function _getUnderlyingAsset(address vToken) private view notNullAddress(vToken) returns (address asset) {\n if (vToken == nativeMarket) {\n asset = NATIVE_TOKEN_ADDR;\n } else if (vToken == vai) {\n asset = vai;\n } else {\n asset = VBep20Interface(vToken).underlying();\n }\n }\n\n /**\n * @dev This function checks if the asset price should be cached\n * @param asset asset address\n * @return bool true if caching is enabled, false otherwise\n */\n function _isCacheEnabled(address asset) private view returns (bool) {\n return tokenConfigs[asset].cachingEnabled;\n }\n}\n" + }, + "contracts/test/BEP20Harness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract BEP20Harness is ERC20 {\n uint8 public decimalsInternal = 18;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {\n decimalsInternal = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function decimals() public view virtual override returns (uint8) {\n return decimalsInternal;\n }\n}\n" + }, + "contracts/test/DeviationBoundedOracleCaller.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IDeviationBoundedOracle } from \"../interfaces/IDeviationBoundedOracle.sol\";\n\n/// @notice Test helper that batches DeviationBoundedOracle calls in a single transaction\n/// so transient storage (tstore/tload) cache can be tested.\ncontract DeviationBoundedOracleCaller {\n IDeviationBoundedOracle public immutable oracle;\n\n constructor(address _oracle) {\n oracle = IDeviationBoundedOracle(_oracle);\n }\n\n function updateAndGetCollateralPrice(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedCollateralPriceView(vToken);\n }\n\n function updateAndGetDebtPrice(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedDebtPriceView(vToken);\n }\n\n function updateAndGetBothPrices(address vToken) external returns (uint256 collateral, uint256 debt) {\n oracle.updateProtectionState(vToken);\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n function updateThenNonViewCollateral(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedCollateralPrice(vToken);\n }\n\n function twoConsecutiveNonViewCollateral(address vToken) external returns (uint256 first, uint256 second) {\n first = oracle.getBoundedCollateralPrice(vToken);\n second = oracle.getBoundedCollateralPrice(vToken);\n }\n\n /// @notice Non-view wrapper so smock records oracle calls made by the view functions.\n function getViewPricesWithoutUpdateNonView(address vToken) external returns (uint256 collateral, uint256 debt) {\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n /// @notice Calls updateProtectionState on vTokenA, then getBoundedCollateralPriceView on vTokenB.\n function updateAViewB(address vTokenA, address vTokenB) external returns (uint256) {\n oracle.updateProtectionState(vTokenA);\n return oracle.getBoundedCollateralPriceView(vTokenB);\n }\n\n function getViewPricesWithoutUpdate(address vToken) external view returns (uint256 collateral, uint256 debt) {\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n}\n" + }, + "contracts/test/MockAnkrBNB.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IAnkrBNB } from \"../interfaces/IAnkrBNB.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAnkrBNB is ERC20, Ownable, IAnkrBNB {\n uint8 private immutable _decimals;\n uint256 public exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setSharesToBonds(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function sharesToBonds(uint256 amount) external view override returns (uint256) {\n return (amount * exchangeRate) / (10 ** uint256(_decimals));\n }\n\n function decimals() public view virtual override(ERC20, IAnkrBNB) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockAsBNB.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IAsBNB } from \"../interfaces/IAsBNB.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAsBNB is ERC20, Ownable, IAsBNB {\n uint8 private immutable _decimals;\n address public minter;\n\n constructor(\n string memory name_,\n string memory symbol_,\n uint8 decimals_,\n address minter_\n ) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n minter = minter_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setMinter(address minter_) external onlyOwner {\n minter = minter_;\n }\n\n function decimals() public view virtual override(ERC20, IAsBNB) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockAsBNBMinter.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { IAsBNBMinter } from \"../interfaces/IAsBNBMinter.sol\";\n\ncontract MockAsBNBMinter is IAsBNBMinter {\n function convertToTokens(uint256 _amount) external pure override returns (uint256) {\n return _amount;\n }\n}\n" + }, + "contracts/test/MockCallPrice.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { OracleInterface, ResilientOracleInterface } from \"../interfaces/OracleInterface.sol\";\n\ninterface CorrelatedTokenOracleInterface {\n function updateSnapshot() external;\n function getPrice(address asset) external view returns (uint256);\n}\n\ncontract MockCallPrice {\n function getMultiPrice(CorrelatedTokenOracleInterface oracle, address asset) public returns (uint256, uint256) {\n oracle.updateSnapshot();\n return (oracle.getPrice(asset), oracle.getPrice(asset));\n }\n\n function getUnderlyingPriceResilientOracle(\n ResilientOracleInterface oracle,\n address vToken\n ) public returns (uint256, uint256) {\n oracle.updatePrice(vToken);\n oracle.updatePrice(vToken);\n return (oracle.getUnderlyingPrice(vToken), oracle.getUnderlyingPrice(vToken));\n }\n\n function getAssetPriceResilientOracle(\n ResilientOracleInterface oracle,\n address asset\n ) public returns (uint256, uint256) {\n oracle.updateAssetPrice(asset);\n oracle.updateAssetPrice(asset);\n return (oracle.getPrice(asset), oracle.getPrice(asset));\n }\n}\n" + }, + "contracts/test/MockEtherFiLiquidityPool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/IEtherFiLiquidityPool.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockEtherFiLiquidityPool is IEtherFiLiquidityPool, Ownable {\n /// @notice The amount of eETH per weETH scaled by 1e18\n uint256 public amountPerShare;\n\n constructor() Ownable() {}\n\n function setAmountPerShare(uint256 _amountPerShare) external onlyOwner {\n amountPerShare = _amountPerShare;\n }\n\n function amountForShare(uint256 _share) external view override returns (uint256) {\n return (_share * amountPerShare) / 1e18;\n }\n}\n" + }, + "contracts/test/MockSFrax.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { ISFrax } from \"../interfaces/ISFrax.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockSFrax is ERC20, Ownable, ISFrax {\n uint8 private immutable _decimals;\n uint256 public exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setRate(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function convertToAssets(uint256 shares) external view override returns (uint256) {\n return (shares * exchangeRate) / (10 ** uint256(_decimals));\n }\n\n function decimals() public view virtual override(ERC20, ISFrax) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockSimpleOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/OracleInterface.sol\";\n\ncontract MockSimpleOracle is OracleInterface {\n mapping(address => uint256) public prices;\n\n constructor() {\n //\n }\n\n function getUnderlyingPrice(address vToken) external view returns (uint256) {\n return prices[vToken];\n }\n\n function getPrice(address asset) external view returns (uint256) {\n return prices[asset];\n }\n\n function setPrice(address vToken, uint256 price) public {\n prices[vToken] = price;\n }\n}\n\ncontract MockBoundValidator is BoundValidatorInterface {\n mapping(address => bool) public validateResults;\n bool public twapUpdated;\n\n constructor() {\n //\n }\n\n function validatePriceWithAnchorPrice(\n address vToken,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool) {\n return validateResults[vToken];\n }\n\n function validateAssetPriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool) {\n return validateResults[asset];\n }\n\n function setValidateResult(address token, bool pass) public {\n validateResults[token] = pass;\n }\n}\n" + }, + "contracts/test/MockV3Aggregator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol\";\n\n/**\n * @title MockV3Aggregator\n * @notice Based on the FluxAggregator contract\n * @notice Use this contract when you need to test\n * other contract's ability to read data from an\n * aggregator contract, but how the aggregator got\n * its answer is unimportant\n */\ncontract MockV3Aggregator is AggregatorV2V3Interface {\n uint256 public constant version = 0;\n\n uint8 public decimals;\n int256 public latestAnswer;\n uint256 public latestTimestamp;\n uint256 public latestRound;\n\n mapping(uint256 => int256) public getAnswer;\n mapping(uint256 => uint256) public getTimestamp;\n mapping(uint256 => uint256) private getStartedAt;\n\n constructor(uint8 _decimals, int256 _initialAnswer) {\n decimals = _decimals;\n updateAnswer(_initialAnswer);\n }\n\n function getRoundData(\n uint80 _roundId\n )\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId);\n }\n\n function latestRoundData()\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n return (\n uint80(latestRound),\n getAnswer[latestRound],\n getStartedAt[latestRound],\n getTimestamp[latestRound],\n uint80(latestRound)\n );\n }\n\n function description() external pure returns (string memory) {\n return \"v0.6/tests/MockV3Aggregator.sol\";\n }\n\n function updateAnswer(int256 _answer) public {\n latestAnswer = _answer;\n latestTimestamp = block.timestamp;\n latestRound++;\n getAnswer[latestRound] = _answer;\n getTimestamp[latestRound] = block.timestamp;\n getStartedAt[latestRound] = block.timestamp;\n }\n\n function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public {\n latestRound = _roundId;\n latestAnswer = _answer;\n latestTimestamp = _timestamp;\n getAnswer[latestRound] = _answer;\n getTimestamp[latestRound] = _timestamp;\n getStartedAt[latestRound] = _startedAt;\n }\n}\n" + }, + "contracts/test/MockWBETH.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IWBETH } from \"../interfaces/IWBETH.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockWBETH is ERC20, Ownable, IWBETH {\n uint8 private immutable _decimals;\n uint256 public override exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setExchangeRate(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function decimals() public view virtual override(ERC20, IWBETH) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/oracles/MockCorrelatedTokenOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"../../oracles/common/CorrelatedTokenOracle.sol\";\n\ncontract MockCorrelatedTokenOracle is CorrelatedTokenOracle {\n uint256 public mockUnderlyingAmount;\n\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n snapshotGap\n )\n {}\n\n function setMockUnderlyingAmount(uint256 amount) external {\n mockUnderlyingAmount = amount;\n }\n\n function getUnderlyingAmount() public view override returns (uint256) {\n return mockUnderlyingAmount;\n }\n}\n" + }, + "contracts/test/oracles/MockERC20.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract MockERC20 is ERC20 {\n constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol) {\n _mint(msg.sender, 100000 * 10 ** uint256(decimals));\n }\n}\n" + }, + "contracts/test/oracles/MockResilientOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/OracleInterface.sol\";\n\ncontract MockOracle is OracleInterface {\n mapping(address => uint256) public prices;\n\n function getPrice(address asset) external view returns (uint256) {\n return prices[asset];\n }\n\n function setPrice(address vToken, uint256 price) public {\n prices[vToken] = price;\n }\n}\n" + }, + "contracts/test/PancakePairHarness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n// a library for performing various math operations\n\nlibrary Math {\n function min(uint256 x, uint256 y) internal pure returns (uint256 z) {\n z = x < y ? x : y;\n }\n\n // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)\n function sqrt(uint256 y) internal pure returns (uint256 z) {\n if (y > 3) {\n z = y;\n uint256 x = y / 2 + 1;\n while (x < z) {\n z = x;\n x = (y / x + x) / 2;\n }\n } else if (y != 0) {\n z = 1;\n }\n }\n}\n\n// range: [0, 2**112 - 1]\n// resolution: 1 / 2**112\n\nlibrary UQ112x112 {\n //solhint-disable-next-line state-visibility\n uint224 constant Q112 = 2 ** 112;\n\n // encode a uint112 as a UQ112x112\n function encode(uint112 y) internal pure returns (uint224 z) {\n z = uint224(y) * Q112; // never overflows\n }\n\n // divide a UQ112x112 by a uint112, returning a UQ112x112\n function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {\n z = x / uint224(y);\n }\n}\n\ncontract PancakePairHarness {\n using UQ112x112 for uint224;\n\n address public token0;\n address public token1;\n\n uint112 private reserve0; // uses single storage slot, accessible via getReserves\n uint112 private reserve1; // uses single storage slot, accessible via getReserves\n uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves\n\n uint256 public price0CumulativeLast;\n uint256 public price1CumulativeLast;\n uint256 public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event\n\n // called once by the factory at time of deployment\n function initialize(address _token0, address _token1) external {\n token0 = _token0;\n token1 = _token1;\n }\n\n // update reserves and, on the first call per block, price accumulators\n function update(uint256 balance0, uint256 balance1, uint112 _reserve0, uint112 _reserve1) external {\n require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, \"PancakeV2: OVERFLOW\");\n uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);\n unchecked {\n uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired\n if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {\n // * never overflows, and + overflow is desired\n price0CumulativeLast += uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;\n price1CumulativeLast += uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;\n }\n }\n reserve0 = uint112(balance0);\n reserve1 = uint112(balance1);\n blockTimestampLast = blockTimestamp;\n }\n\n function currentBlockTimestamp() external view returns (uint32) {\n return uint32(block.timestamp % 2 ** 32);\n }\n\n function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {\n _reserve0 = reserve0;\n _reserve1 = reserve1;\n _blockTimestampLast = blockTimestampLast;\n }\n}\n" + }, + "contracts/test/VBEP20Harness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"./BEP20Harness.sol\";\n\ncontract VBEP20Harness is BEP20Harness {\n /**\n * @notice Underlying asset for this VToken\n */\n address public underlying;\n\n constructor(\n string memory name_,\n string memory symbol_,\n uint8 decimals,\n address underlying_\n ) BEP20Harness(name_, symbol_, decimals) {\n underlying = underlying_;\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor (address initialOwner) {\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\n * proxy whose upgrades are fully controlled by the current implementation.\n */\ninterface IERC1822Proxiable {\n /**\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\n * address.\n *\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\n * function revert if invoked through a proxy.\n */\n function proxiableUUID() external view returns (bytes32);\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\n */\ninterface IBeacon {\n /**\n * @dev Must return an address that can be used as a delegate call target.\n *\n * {BeaconProxy} will check that this address is a contract.\n */\n function implementation() external view returns (address);\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../Proxy.sol\";\nimport \"./ERC1967Upgrade.sol\";\n\n/**\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\n * implementation address that can be changed. This address is stored in storage in the location specified by\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\n * implementation behind the proxy.\n */\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\n /**\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\n *\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\n */\n constructor(address _logic, bytes memory _data) payable {\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.implementation\")) - 1));\n _upgradeToAndCall(_logic, _data, false);\n }\n\n /**\n * @dev Returns the current implementation address.\n */\n function _implementation() internal view virtual override returns (address impl) {\n return ERC1967Upgrade._getImplementation();\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../beacon/IBeacon.sol\";\nimport \"../../interfaces/draft-IERC1822.sol\";\nimport \"../../utils/Address.sol\";\nimport \"../../utils/StorageSlot.sol\";\n\n/**\n * @dev This abstract contract provides getters and event emitting update functions for\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\n *\n * _Available since v4.1._\n *\n * @custom:oz-upgrades-unsafe-allow delegatecall\n */\nabstract contract ERC1967Upgrade {\n // This is the keccak-256 hash of \"eip1967.proxy.rollback\" subtracted by 1\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\n\n /**\n * @dev Storage slot with the address of the current implementation.\n * This is the keccak-256 hash of \"eip1967.proxy.implementation\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n\n /**\n * @dev Emitted when the implementation is upgraded.\n */\n event Upgraded(address indexed implementation);\n\n /**\n * @dev Returns the current implementation address.\n */\n function _getImplementation() internal view returns (address) {\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n }\n\n /**\n * @dev Stores a new address in the EIP1967 implementation slot.\n */\n function _setImplementation(address newImplementation) private {\n require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n }\n\n /**\n * @dev Perform implementation upgrade\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeTo(address newImplementation) internal {\n _setImplementation(newImplementation);\n emit Upgraded(newImplementation);\n }\n\n /**\n * @dev Perform implementation upgrade with additional setup call.\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeToAndCall(\n address newImplementation,\n bytes memory data,\n bool forceCall\n ) internal {\n _upgradeTo(newImplementation);\n if (data.length > 0 || forceCall) {\n Address.functionDelegateCall(newImplementation, data);\n }\n }\n\n /**\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeToAndCallUUPS(\n address newImplementation,\n bytes memory data,\n bool forceCall\n ) internal {\n // Upgrades from old implementations will perform a rollback test. This test requires the new\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\n // this special case will break upgrade paths from old UUPS implementation to new ones.\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\n _setImplementation(newImplementation);\n } else {\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\n require(slot == _IMPLEMENTATION_SLOT, \"ERC1967Upgrade: unsupported proxiableUUID\");\n } catch {\n revert(\"ERC1967Upgrade: new implementation is not UUPS\");\n }\n _upgradeToAndCall(newImplementation, data, forceCall);\n }\n }\n\n /**\n * @dev Storage slot with the admin of the contract.\n * This is the keccak-256 hash of \"eip1967.proxy.admin\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n /**\n * @dev Emitted when the admin account has changed.\n */\n event AdminChanged(address previousAdmin, address newAdmin);\n\n /**\n * @dev Returns the current admin.\n */\n function _getAdmin() internal view virtual returns (address) {\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\n }\n\n /**\n * @dev Stores a new address in the EIP1967 admin slot.\n */\n function _setAdmin(address newAdmin) private {\n require(newAdmin != address(0), \"ERC1967: new admin is the zero address\");\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\n }\n\n /**\n * @dev Changes the admin of the proxy.\n *\n * Emits an {AdminChanged} event.\n */\n function _changeAdmin(address newAdmin) internal {\n emit AdminChanged(_getAdmin(), newAdmin);\n _setAdmin(newAdmin);\n }\n\n /**\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\n */\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\n\n /**\n * @dev Emitted when the beacon is upgraded.\n */\n event BeaconUpgraded(address indexed beacon);\n\n /**\n * @dev Returns the current beacon.\n */\n function _getBeacon() internal view returns (address) {\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\n }\n\n /**\n * @dev Stores a new beacon in the EIP1967 beacon slot.\n */\n function _setBeacon(address newBeacon) private {\n require(Address.isContract(newBeacon), \"ERC1967: new beacon is not a contract\");\n require(Address.isContract(IBeacon(newBeacon).implementation()), \"ERC1967: beacon implementation is not a contract\");\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\n }\n\n /**\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\n *\n * Emits a {BeaconUpgraded} event.\n */\n function _upgradeBeaconToAndCall(\n address newBeacon,\n bytes memory data,\n bool forceCall\n ) internal {\n _setBeacon(newBeacon);\n emit BeaconUpgraded(newBeacon);\n if (data.length > 0 || forceCall) {\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/Proxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\n * be specified by overriding the virtual {_implementation} function.\n *\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\n * different contract through the {_delegate} function.\n *\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\n */\nabstract contract Proxy {\n /**\n * @dev Delegates the current call to `implementation`.\n *\n * This function does not return to its internal call site, it will return directly to the external caller.\n */\n function _delegate(address implementation) internal virtual {\n assembly {\n // Copy msg.data. We take full control of memory in this inline assembly\n // block because it will not return to Solidity code. We overwrite the\n // Solidity scratch pad at memory position 0.\n calldatacopy(0, 0, calldatasize())\n\n // Call the implementation.\n // out and outsize are 0 because we don't know the size yet.\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\n\n // Copy the returned data.\n returndatacopy(0, 0, returndatasize())\n\n switch result\n // delegatecall returns 0 on error.\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n\n /**\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\n * and {_fallback} should delegate.\n */\n function _implementation() internal view virtual returns (address);\n\n /**\n * @dev Delegates the current call to the address returned by `_implementation()`.\n *\n * This function does not return to its internall call site, it will return directly to the external caller.\n */\n function _fallback() internal virtual {\n _beforeFallback();\n _delegate(_implementation());\n }\n\n /**\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\n * function in the contract matches the call data.\n */\n fallback() external payable virtual {\n _fallback();\n }\n\n /**\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\n * is empty.\n */\n receive() external payable virtual {\n _fallback();\n }\n\n /**\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\n * call, or as part of the Solidity `fallback` or `receive` functions.\n *\n * If overriden should call `super._beforeFallback()`.\n */\n function _beforeFallback() internal virtual {}\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./TransparentUpgradeableProxy.sol\";\nimport \"../../access/Ownable.sol\";\n\n/**\n * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an\n * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.\n */\ncontract ProxyAdmin is Ownable {\n\n constructor (address initialOwner) Ownable(initialOwner) {}\n\n /**\n * @dev Returns the current implementation of `proxy`.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {\n // We need to manually run the static call since the getter cannot be flagged as view\n // bytes4(keccak256(\"implementation()\")) == 0x5c60da1b\n (bool success, bytes memory returndata) = address(proxy).staticcall(hex\"5c60da1b\");\n require(success);\n return abi.decode(returndata, (address));\n }\n\n /**\n * @dev Returns the current admin of `proxy`.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {\n // We need to manually run the static call since the getter cannot be flagged as view\n // bytes4(keccak256(\"admin()\")) == 0xf851a440\n (bool success, bytes memory returndata) = address(proxy).staticcall(hex\"f851a440\");\n require(success);\n return abi.decode(returndata, (address));\n }\n\n /**\n * @dev Changes the admin of `proxy` to `newAdmin`.\n *\n * Requirements:\n *\n * - This contract must be the current admin of `proxy`.\n */\n function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {\n proxy.changeAdmin(newAdmin);\n }\n\n /**\n * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {\n proxy.upgradeTo(implementation);\n }\n\n /**\n * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See\n * {TransparentUpgradeableProxy-upgradeToAndCall}.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function upgradeAndCall(\n TransparentUpgradeableProxy proxy,\n address implementation,\n bytes memory data\n ) public payable virtual onlyOwner {\n proxy.upgradeToAndCall{value: msg.value}(implementation, data);\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../ERC1967/ERC1967Proxy.sol\";\n\n/**\n * @dev This contract implements a proxy that is upgradeable by an admin.\n *\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\n * clashing], which can potentially be used in an attack, this contract uses the\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\n * things that go hand in hand:\n *\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\n * that call matches one of the admin functions exposed by the proxy itself.\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\n * \"admin cannot fallback to proxy target\".\n *\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\n * to sudden errors when trying to call a function from the proxy implementation.\n *\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\n */\ncontract TransparentUpgradeableProxy is ERC1967Proxy {\n /**\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\n */\n constructor(\n address _logic,\n address admin_,\n bytes memory _data\n ) payable ERC1967Proxy(_logic, _data) {\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n _changeAdmin(admin_);\n }\n\n /**\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\n */\n modifier ifAdmin() {\n if (msg.sender == _getAdmin()) {\n _;\n } else {\n _fallback();\n }\n }\n\n /**\n * @dev Returns the current admin.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\n */\n function admin() external ifAdmin returns (address admin_) {\n admin_ = _getAdmin();\n }\n\n /**\n * @dev Returns the current implementation.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\n */\n function implementation() external ifAdmin returns (address implementation_) {\n implementation_ = _implementation();\n }\n\n /**\n * @dev Changes the admin of the proxy.\n *\n * Emits an {AdminChanged} event.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.\n */\n function changeAdmin(address newAdmin) external virtual ifAdmin {\n _changeAdmin(newAdmin);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\n */\n function upgradeTo(address newImplementation) external ifAdmin {\n _upgradeToAndCall(newImplementation, bytes(\"\"), false);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\n * proxied contract.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\n _upgradeToAndCall(newImplementation, data, true);\n }\n\n /**\n * @dev Returns the current admin.\n */\n function _admin() internal view virtual returns (address) {\n return _getAdmin();\n }\n\n /**\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\n */\n function _beforeFallback() internal virtual override {\n require(msg.sender != _getAdmin(), \"TransparentUpgradeableProxy: admin cannot fallback to proxy target\");\n super._beforeFallback();\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/StorageSlot.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC1967 implementation slot:\n * ```\n * contract ERC1967 {\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\";\n\n/**\n * @dev This contract implements a proxy that is upgradeable by an admin.\n *\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\n * clashing], which can potentially be used in an attack, this contract uses the\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\n * things that go hand in hand:\n *\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\n * that call matches one of the admin functions exposed by the proxy itself.\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\n * \"admin cannot fallback to proxy target\".\n *\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\n * to sudden errors when trying to call a function from the proxy implementation.\n *\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\n */\ncontract OptimizedTransparentUpgradeableProxy is ERC1967Proxy {\n address internal immutable _ADMIN;\n\n /**\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\n */\n constructor(\n address _logic,\n address admin_,\n bytes memory _data\n ) payable ERC1967Proxy(_logic, _data) {\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n _ADMIN = admin_;\n\n // still store it to work with EIP-1967\n bytes32 slot = _ADMIN_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(slot, admin_)\n }\n emit AdminChanged(address(0), admin_);\n }\n\n /**\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\n */\n modifier ifAdmin() {\n if (msg.sender == _getAdmin()) {\n _;\n } else {\n _fallback();\n }\n }\n\n /**\n * @dev Returns the current admin.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\n */\n function admin() external ifAdmin returns (address admin_) {\n admin_ = _getAdmin();\n }\n\n /**\n * @dev Returns the current implementation.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\n */\n function implementation() external ifAdmin returns (address implementation_) {\n implementation_ = _implementation();\n }\n\n /**\n * @dev Upgrade the implementation of the proxy.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\n */\n function upgradeTo(address newImplementation) external ifAdmin {\n _upgradeToAndCall(newImplementation, bytes(\"\"), false);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\n * proxied contract.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\n _upgradeToAndCall(newImplementation, data, true);\n }\n\n /**\n * @dev Returns the current admin.\n */\n function _admin() internal view virtual returns (address) {\n return _getAdmin();\n }\n\n /**\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\n */\n function _beforeFallback() internal virtual override {\n require(msg.sender != _getAdmin(), \"TransparentUpgradeableProxy: admin cannot fallback to proxy target\");\n super._beforeFallback();\n }\n\n function _getAdmin() internal view virtual override returns (address) {\n return _ADMIN;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200, + "details": { + "yul": true + } + }, + "evmVersion": "cancun", + "outputSelection": { + "*": { + "*": [ + "storageLayout", + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "evm.gasEstimates" + ], + "": ["ast"] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} diff --git a/deployments/bsctestnet/solcInputs/fad126030085a626952462c92d5fc265.json b/deployments/bsctestnet/solcInputs/fad126030085a626952462c92d5fc265.json new file mode 100644 index 00000000..1e5482b8 --- /dev/null +++ b/deployments/bsctestnet/solcInputs/fad126030085a626952462c92d5fc265.json @@ -0,0 +1,349 @@ +{ + "language": "Solidity", + "sources": { + "@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorInterface {\n function latestAnswer() external view returns (int256);\n\n function latestTimestamp() external view returns (uint256);\n\n function latestRound() external view returns (uint256);\n\n function getAnswer(uint256 roundId) external view returns (int256);\n\n function getTimestamp(uint256 roundId) external view returns (uint256);\n\n event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt);\n\n event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);\n}\n" + }, + "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./AggregatorInterface.sol\";\nimport \"./AggregatorV3Interface.sol\";\n\ninterface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface {}\n" + }, + "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n function decimals() external view returns (uint8);\n\n function description() external view returns (string memory);\n\n function version() external view returns (uint256);\n\n function getRoundData(uint80 _roundId)\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./OwnableUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n function __Ownable2Step_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable2Step_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() public virtual {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n function __Ownable_init() internal onlyInitializing {\n __Ownable_init_unchained();\n }\n\n function __Ownable_init_unchained() internal onlyInitializing {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../../utils/AddressUpgradeable.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n * @custom:oz-retyped-from bool\n */\n uint8 private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint8 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts.\n *\n * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a\n * constructor.\n *\n * Emits an {Initialized} event.\n */\n modifier initializer() {\n bool isTopLevelCall = !_initializing;\n require(\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\n \"Initializable: contract is already initialized\"\n );\n _initialized = 1;\n if (isTopLevelCall) {\n _initializing = true;\n }\n _;\n if (isTopLevelCall) {\n _initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\n * are added through upgrades and that require initialization.\n *\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\n * cannot be nested. If one is invoked in the context of another, execution will revert.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n *\n * WARNING: setting the version to 255 will prevent any future reinitialization.\n *\n * Emits an {Initialized} event.\n */\n modifier reinitializer(uint8 version) {\n require(!_initializing && _initialized < version, \"Initializable: contract is already initialized\");\n _initialized = version;\n _initializing = true;\n _;\n _initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n *\n * Emits an {Initialized} event the first time it is successfully executed.\n */\n function _disableInitializers() internal virtual {\n require(!_initializing, \"Initializable: contract is initializing\");\n if (_initialized != type(uint8).max) {\n _initialized = type(uint8).max;\n emit Initialized(type(uint8).max);\n }\n }\n\n /**\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\n */\n function _getInitializedVersion() internal view returns (uint8) {\n return _initialized;\n }\n\n /**\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\n */\n function _isInitializing() internal view returns (bool) {\n return _initializing;\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/ContextUpgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract PausableUpgradeable is Initializable, ContextUpgradeable {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n function __Pausable_init() internal onlyInitializing {\n __Pausable_init_unchained();\n }\n\n function __Pausable_init_unchained() internal onlyInitializing {\n _paused = false;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n _requireNotPaused();\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n _requirePaused();\n _;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Throws if the contract is paused.\n */\n function _requireNotPaused() internal view virtual {\n require(!paused(), \"Pausable: paused\");\n }\n\n /**\n * @dev Throws if the contract is not paused.\n */\n function _requirePaused() internal view virtual {\n require(paused(), \"Pausable: not paused\");\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary AddressUpgradeable {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n *\n * Furthermore, `isContract` will also return true if the target contract within\n * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,\n * which only has an effect at the end of a transaction.\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n" + }, + "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n" + }, + "@openzeppelin/contracts/access/IAccessControl.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n" + }, + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/ERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC20.sol\";\nimport \"./extensions/IERC20Metadata.sol\";\nimport \"../../utils/Context.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(address from, address to, uint256 amount) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(address owner, address spender, uint256 amount) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 amount) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\";\nimport \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\n\nimport \"./IAccessControlManagerV8.sol\";\n\n/**\n * @title AccessControlledV8\n * @author Venus\n * @notice This contract is helper between access control manager and actual contract. This contract further inherited by other contract (using solidity 0.8.13)\n * to integrate access controlled mechanism. It provides initialise methods and verifying access methods.\n */\nabstract contract AccessControlledV8 is Initializable, Ownable2StepUpgradeable {\n /// @notice Access control manager contract\n IAccessControlManagerV8 internal _accessControlManager;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[49] private __gap;\n\n /// @notice Emitted when access control manager contract address is changed\n event NewAccessControlManager(address oldAccessControlManager, address newAccessControlManager);\n\n /// @notice Thrown when the action is prohibited by AccessControlManager\n error Unauthorized(address sender, address calledContract, string methodSignature);\n\n function __AccessControlled_init(address accessControlManager_) internal onlyInitializing {\n __Ownable2Step_init();\n __AccessControlled_init_unchained(accessControlManager_);\n }\n\n function __AccessControlled_init_unchained(address accessControlManager_) internal onlyInitializing {\n _setAccessControlManager(accessControlManager_);\n }\n\n /**\n * @notice Sets the address of AccessControlManager\n * @dev Admin function to set address of AccessControlManager\n * @param accessControlManager_ The new address of the AccessControlManager\n * @custom:event Emits NewAccessControlManager event\n * @custom:access Only Governance\n */\n function setAccessControlManager(address accessControlManager_) external onlyOwner {\n _setAccessControlManager(accessControlManager_);\n }\n\n /**\n * @notice Returns the address of the access control manager contract\n */\n function accessControlManager() external view returns (IAccessControlManagerV8) {\n return _accessControlManager;\n }\n\n /**\n * @dev Internal function to set address of AccessControlManager\n * @param accessControlManager_ The new address of the AccessControlManager\n */\n function _setAccessControlManager(address accessControlManager_) internal {\n require(address(accessControlManager_) != address(0), \"invalid acess control manager address\");\n address oldAccessControlManager = address(_accessControlManager);\n _accessControlManager = IAccessControlManagerV8(accessControlManager_);\n emit NewAccessControlManager(oldAccessControlManager, accessControlManager_);\n }\n\n /**\n * @notice Reverts if the call is not allowed by AccessControlManager\n * @param signature Method signature\n */\n function _checkAccessAllowed(string memory signature) internal view {\n bool isAllowedToCall = _accessControlManager.isAllowedToCall(msg.sender, signature);\n\n if (!isAllowedToCall) {\n revert Unauthorized(msg.sender, address(this), signature);\n }\n }\n}\n" + }, + "@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/access/IAccessControl.sol\";\n\n/**\n * @title IAccessControlManagerV8\n * @author Venus\n * @notice Interface implemented by the `AccessControlManagerV8` contract.\n */\ninterface IAccessControlManagerV8 is IAccessControl {\n function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external;\n\n function revokeCallPermission(\n address contractAddress,\n string calldata functionSig,\n address accountToRevoke\n ) external;\n\n function isAllowedToCall(address account, string calldata functionSig) external view returns (bool);\n\n function hasPermission(\n address account,\n address contractAddress,\n string calldata functionSig\n ) external view returns (bool);\n}\n" + }, + "@venusprotocol/solidity-utilities/contracts/constants.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\n/// @dev Base unit for computations, usually used in scaling (multiplications, divisions)\nuint256 constant EXP_SCALE = 1e18;\n\n/// @dev A unit (literal one) in EXP_SCALE, usually used in additions/subtractions\nuint256 constant MANTISSA_ONE = EXP_SCALE;\n\n/// @dev The approximate number of seconds per year\nuint256 constant SECONDS_PER_YEAR = 31_536_000;\n" + }, + "@venusprotocol/solidity-utilities/contracts/validators.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n/// @notice Thrown if the supplied address is a zero address where it is not allowed\nerror ZeroAddressNotAllowed();\n\n/// @notice Thrown if the supplied value is 0 where it is not allowed\nerror ZeroValueNotAllowed();\n\n/// @notice Checks if the provided address is nonzero, reverts otherwise\n/// @param address_ Address to check\n/// @custom:error ZeroAddressNotAllowed is thrown if the provided address is a zero address\nfunction ensureNonzeroAddress(address address_) pure {\n if (address_ == address(0)) {\n revert ZeroAddressNotAllowed();\n }\n}\n\n/// @notice Checks if the provided value is nonzero, reverts otherwise\n/// @param value_ Value to check\n/// @custom:error ZeroValueNotAllowed is thrown if the provided value is 0\nfunction ensureNonzeroValue(uint256 value_) pure {\n if (value_ == 0) {\n revert ZeroValueNotAllowed();\n }\n}\n" + }, + "contracts/DeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2024 Venus\npragma solidity 0.8.25;\n\nimport { VBep20Interface } from \"./interfaces/VBep20Interface.sol\";\nimport { ResilientOracleInterface } from \"./interfaces/OracleInterface.sol\";\nimport { IDeviationBoundedOracle } from \"./interfaces/IDeviationBoundedOracle.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { Transient } from \"./lib/Transient.sol\";\n\n/**\n * @title DeviationBoundedOracle\n * @author Venus\n * @notice The DeviationBoundedOracle provides manipulation-resistant pricing for lending operations.\n *\n * It maintains a per-market rolling min/max price window. When the current spot price deviates\n * significantly from the window bounds, protection mode activates automatically and conservative\n * pricing kicks in:\n * - Collateral is valued at min(spot, windowMin) — caps collateral value at recent window low\n * - Debt is valued at max(spot, windowMax) — floors debt value at recent window high\n *\n * This protects against instantaneous or short-duration price manipulation attacks on low-liquidity\n * collateral tokens. Sustained attacks beyond the window period are expected to be handled by\n * off-chain monitoring systems.\n *\n * The oracle exposes both view and non-view price functions. The non-view variants update the\n * price window and trigger protection. The view variants read stored state only. A transient\n * price cache avoids redundant ResilientOracle calls within the same transaction when\n * updateProtectionState is called before the view price reads.\n */\ncontract DeviationBoundedOracle is AccessControlledV8, IDeviationBoundedOracle {\n /// @notice Minimum allowed threshold value (5%) to account for keeper deadband\n uint256 public constant MIN_THRESHOLD = 5e16;\n\n /// @notice Maximum allowed threshold value (50%)\n uint256 public constant MAX_THRESHOLD = 50e16;\n\n /// @notice Keeper deadband threshold (5%) — min/max corrections below this are suppressed\n uint256 public constant KEEPER_DEADBAND = 5e16;\n\n /// @notice Resilient Oracle used to fetch spot prices\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Native market address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable nativeMarket;\n\n /// @notice VAI address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vai;\n\n /// @notice Transient storage slot for caching final collateral prices within a transaction\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/cache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/cache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant COLLATERAL_PRICE_CACHE_SLOT =\n 0x818cfa9b1e1b1cc716656acdb79a94121ed79bfb196bf958683ed2a3277cb200;\n\n /// @notice Transient storage slot for caching final debt prices within a transaction\n /// @dev custom:storage-location erc7201:venus-protocol/oracle/DeviationBoundedOracle/debtCache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/DeviationBoundedOracle/debtCache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant DEBT_PRICE_CACHE_SLOT = 0x84d6ca795decc666d3d1d524cbbadd8a1e0e0279db766fe7a1b41b1eb8970600;\n\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\n /// and can serve as any underlying asset of a market that supports native tokens\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Per-asset protection state\n mapping(address => MarketProtectionState) public assetProtectionConfig;\n\n /// @notice Append-only array of all assets ever initialized, used for enumeration\n address[] public allAssets;\n\n /// @notice Storage gap for upgrades\n uint256[48] private __gap;\n\n /**\n * @notice Constructor for the implementation contract. Sets immutable variables.\n * @param _resilientOracle Address of the ResilientOracle contract\n * @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\n * @param vaiAddress The address of the VAI token (if there is VAI on the deployed chain).\n * @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(ResilientOracleInterface _resilientOracle, address nativeMarketAddress, address vaiAddress) {\n ensureNonzeroAddress(address(_resilientOracle));\n ensureNonzeroAddress(nativeMarketAddress);\n RESILIENT_ORACLE = _resilientOracle;\n nativeMarket = nativeMarketAddress;\n vai = vaiAddress;\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n // ----- Non-view price functions (update window + trigger protection) -----\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns the conservative (lower) price when protection is active.\n * Used by keepers or direct callers who want atomic update + read.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice) {\n (collateralPrice, ) = _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns the conservative (higher) price when protection is active.\n * Used by keepers or direct callers who want atomic update + read.\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice) {\n (, debtPrice) = _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @dev Fetches spot from ResilientOracle, updates the price window, checks trigger,\n * and returns both conservative prices in a single call.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice) {\n return _updateAndGetBoundedPrices(vToken);\n }\n\n /**\n * @notice Fetches the spot price, updates the protection window, and caches the resolved\n * bounded prices in transient storage for the duration of the transaction.\n * @dev Call this once per vToken at the start of a transaction (e.g. from PolicyFacet before\n * liquidity calculations). Subsequent calls to getBoundedCollateralPriceView /\n * getBoundedDebtPriceView within the same transaction will read from the transient cache\n * instead of querying ResilientOracle again, keeping those functions as `view` and\n * avoiding redundant oracle calls.\n * @param vToken vToken address\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external {\n _updateAndGetBoundedPrices(vToken);\n }\n\n // ----- View price functions (read stored/cached state only) -----\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first (populated by a prior updateProtectionState call\n * in the same transaction). Falls back to ResilientOracle on cache miss.\n * Returns min(spot, windowMin) when protection is active, spot otherwise.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 collateralPrice) {\n (collateralPrice, ) = _computeBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first (populated by a prior updateProtectionState call\n * in the same transaction). Falls back to ResilientOracle on cache miss.\n * Returns max(spot, windowMax) when protection is active, spot otherwise.\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 debtPrice) {\n (, debtPrice) = _computeBoundedPrices(vToken);\n }\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first; falls back to ResilientOracle on cache miss.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice) {\n return _computeBoundedPrices(vToken);\n }\n\n // ----- Keeper functions -----\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @dev Called by the keeper to push corrected min values from the off-chain sliding window.\n * Constraint: newMin must be at or below the current spot price.\n * @param asset The underlying asset address\n * @param newMin The new minimum price\n * @custom:access Only authorized keeper addresses\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external {\n _checkAccessAllowed(\"updateMinPrice(address,uint128)\");\n _validateAndUpdateBound(asset, newMin, PriceBoundType.MIN);\n }\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @dev Called by the keeper to push corrected max values from the off-chain sliding window.\n * Constraint: newMax must be at or above the current spot price.\n * @param asset The underlying asset address\n * @param newMax The new maximum price\n * @custom:access Only authorized keeper addresses\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external {\n _checkAccessAllowed(\"updateMaxPrice(address,uint128)\");\n _validateAndUpdateBound(asset, newMax, PriceBoundType.MAX);\n }\n\n /**\n * @notice Disables protection mode for a given asset\n * @dev Called by the keeper/monitor after confirming price has normalised.\n * Enforces two conditions on-chain:\n * 1. Cooldown period has elapsed since the last trigger\n * 2. Price range has converged below the exit threshold\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if cooldown period has not elapsed\n * @custom:error PriceRangeNotConverged if window range is still above exit threshold\n * @custom:event ProtectedPriceDisabled\n */\n function disableActiveProtection(address asset) external {\n _checkAccessAllowed(\"disableActiveProtection(address)\");\n\n MarketProtectionState storage state = assetProtectionConfig[asset];\n\n if (!state.isProtectedPriceActive) revert ProtectedPriceInactive(asset);\n\n if (block.timestamp < uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod)) {\n revert CooldownNotElapsed(asset, state.lastProtectionTriggeredAt, state.cooldownPeriod);\n }\n\n // exit protected price if price range has converged below exit threshold\n uint256 rangeRatio = _computePriceBoundRatio(state.minPrice, state.maxPrice);\n if (rangeRatio >= state.resetThreshold) {\n revert PriceRangeNotConverged(asset, rangeRatio, state.resetThreshold);\n }\n\n state.isProtectedPriceActive = false;\n emit ProtectedPriceDisabled(asset);\n }\n\n // ----- Admin functions (governance-gated) -----\n\n /**\n * @notice Initializes protection for a new asset\n * @dev Fetches the current spot price from ResilientOracle to seed the initial min/max window,\n * confirming the oracle is live for this asset before it is listed. Both bounds start at\n * spot so the window expands naturally as prices move. Can only be called once per asset.\n * @param asset The underlying asset address\n * @param cooldownPeriod Minimum time protection stays active after last trigger\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\n * @custom:access Only Governance\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(\n address asset,\n uint64 cooldownPeriod,\n uint256 triggerThreshold,\n uint256 resetThreshold\n ) external {\n _checkAccessAllowed(\"setTokenConfig(address,uint64,uint256,uint256)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(cooldownPeriod);\n ensureNonzeroValue(triggerThreshold);\n ensureNonzeroValue(resetThreshold);\n if (assetProtectionConfig[asset].asset != address(0)) revert MarketAlreadyInitialized(asset);\n if (triggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(triggerThreshold, MIN_THRESHOLD);\n if (triggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(triggerThreshold, MAX_THRESHOLD);\n if (resetThreshold >= triggerThreshold) revert InvalidResetThreshold(resetThreshold);\n if (asset == vai) revert VAINotAllowed();\n\n uint128 spotU128 = _safeToUint128(_fetchSpotPrice(asset));\n\n assetProtectionConfig[asset] = MarketProtectionState({\n minPrice: spotU128,\n maxPrice: spotU128,\n isProtectedPriceActive: false,\n isBoundedPricingEnabled: true,\n lastProtectionTriggeredAt: 0,\n cooldownPeriod: cooldownPeriod,\n asset: asset,\n triggerThreshold: uint128(triggerThreshold),\n resetThreshold: uint128(resetThreshold)\n });\n\n allAssets.push(asset);\n\n emit ProtectionInitialized(asset, spotU128, spotU128, cooldownPeriod, triggerThreshold);\n emit BoundedPricingWhitelistUpdated(asset, true);\n }\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds\n * @custom:access Only Governance\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external {\n _checkAccessAllowed(\"setCooldownPeriod(address,uint64)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(newCooldown);\n\n MarketProtectionState storage state = _ensureInitialized(asset);\n emit CooldownPeriodSet(asset, state.cooldownPeriod, newCooldown);\n state.cooldownPeriod = newCooldown;\n }\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external {\n _checkAccessAllowed(\"setThresholds(address,uint256,uint256)\");\n ensureNonzeroAddress(asset);\n ensureNonzeroValue(newTriggerThreshold);\n ensureNonzeroValue(newResetThreshold);\n if (newTriggerThreshold < MIN_THRESHOLD) revert ThresholdBelowMinimum(newTriggerThreshold, MIN_THRESHOLD);\n if (newTriggerThreshold > MAX_THRESHOLD) revert ThresholdAboveMaximum(newTriggerThreshold, MAX_THRESHOLD);\n if (newResetThreshold >= newTriggerThreshold) revert InvalidResetThreshold(newResetThreshold);\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (newTriggerThreshold != state.triggerThreshold) {\n emit TriggerThresholdSet(asset, state.triggerThreshold, newTriggerThreshold);\n state.triggerThreshold = uint128(newTriggerThreshold);\n }\n if (newResetThreshold != state.resetThreshold) {\n emit ResetThresholdSet(asset, state.resetThreshold, newResetThreshold);\n state.resetThreshold = uint128(newResetThreshold);\n }\n }\n\n /**\n * @notice Sets whether an asset is enabled for bounded pricing\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external {\n _checkAccessAllowed(\"setAssetBoundedPricingEnabled(address,bool)\");\n ensureNonzeroAddress(asset);\n\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n if (!enabled && state.isProtectedPriceActive) {\n revert ProtectedPriceActive(asset);\n }\n\n state.isBoundedPricingEnabled = enabled;\n emit BoundedPricingWhitelistUpdated(asset, enabled);\n }\n\n // ----- View helpers -----\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory) {\n return allAssets;\n }\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool) {\n return assetProtectionConfig[asset].isBoundedPricingEnabled;\n }\n\n /**\n * @notice Checks if protection is currently active for an asset\n * @param asset The underlying asset address\n * @return True if protected price is active\n */\n function isProtectedPriceActive(address asset) external view returns (bool) {\n return assetProtectionConfig[asset].isProtectedPriceActive;\n }\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @dev Iterates the append-only allAssets array and filters by isBoundedPricingEnabled.\n * Gas-free for off-chain callers.\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory) {\n uint256 len = allAssets.length;\n address[] memory temp = new address[](len);\n uint256 count;\n for (uint256 i; i < len; ++i) {\n if (assetProtectionConfig[allAssets[i]].isBoundedPricingEnabled) {\n temp[count++] = allAssets[i];\n }\n }\n address[] memory result = new address[](count);\n for (uint256 i; i < count; ++i) {\n result[i] = temp[i];\n }\n return result;\n }\n\n /**\n * @notice Checks if protection can be exited for an asset\n * @dev Returns true when both conditions are met:\n * 1. Cooldown period has elapsed since last trigger\n * 2. Price range has converged below exit threshold\n * @param asset The underlying asset address\n * @return True if protection can be disabled\n */\n function canExitProtection(address asset) external view returns (bool) {\n MarketProtectionState storage state = assetProtectionConfig[asset];\n return\n state.isProtectedPriceActive &&\n block.timestamp >= uint256(state.lastProtectionTriggeredAt) + uint256(state.cooldownPeriod) &&\n _computePriceBoundRatio(state.minPrice, state.maxPrice) < state.resetThreshold;\n }\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the deadband\n * from the keeper's proposed window values\n * @dev Allows the keeper to identify stale windows in a single call, avoiding N individual reads.\n * Drift formula: |onChain - proposed| / onChain (scaled by EXP_SCALE)\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's off-chain window minimum prices\n * @param proposedMaxs Keeper's off-chain window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate) {\n uint256 len = assets.length;\n if (len != proposedMins.length || len != proposedMaxs.length) revert InvalidArrayLength();\n\n needsMinUpdate = new bool[](len);\n needsMaxUpdate = new bool[](len);\n\n for (uint256 i; i < len; ++i) {\n MarketProtectionState storage state = assetProtectionConfig[assets[i]];\n needsMinUpdate[i] = _exceedsCorrectionDeadband(state.minPrice, proposedMins[i]);\n needsMaxUpdate[i] = _exceedsCorrectionDeadband(state.maxPrice, proposedMaxs[i]);\n }\n }\n\n // ----- Internal functions -----\n\n /**\n * @notice Validates and applies a keeper-provided min or max price update\n * @param asset The underlying asset address\n * @param newPrice The new price value to set\n * @param boundType Whether this is a MIN or MAX bound update\n * @custom:error ZeroPriceNotAllowed if newPrice is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if boundType is MIN and newPrice exceeds the current spot or is at or above maxPrice\n * @custom:error InvalidMaxPrice if boundType is MAX and newPrice is below the current spot or is at or below minPrice\n */\n function _validateAndUpdateBound(address asset, uint128 newPrice, PriceBoundType boundType) internal {\n ensureNonzeroAddress(asset);\n if (newPrice == 0) revert ZeroPriceNotAllowed();\n MarketProtectionState storage state = _ensureInitialized(asset);\n\n uint256 currentSpot = _fetchSpotPrice(asset);\n if (boundType == PriceBoundType.MIN) {\n if (newPrice >= state.maxPrice || uint256(newPrice) > currentSpot)\n revert InvalidMinPrice(asset, newPrice, currentSpot);\n _setMinPrice(state, asset, newPrice);\n } else if (boundType == PriceBoundType.MAX) {\n if (newPrice <= state.minPrice || uint256(newPrice) < currentSpot)\n revert InvalidMaxPrice(asset, newPrice, currentSpot);\n _setMaxPrice(state, asset, newPrice);\n }\n }\n\n /**\n * @notice Shared non-view logic for all bounded price functions.\n * Fetches spot, updates window, triggers protection if needed, and returns both bounded prices.\n * @param vToken vToken address\n * @return minPrice The bounded lower (collateral) price\n * @return maxPrice The bounded upper (debt) price\n */\n function _updateAndGetBoundedPrices(address vToken) internal returns (uint256 minPrice, uint256 maxPrice) {\n address asset = _getUnderlyingAsset(vToken);\n\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\n (minPrice, maxPrice) = _getCachedPrices(asset);\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\n\n // return early if failure from resilient oracle to prevent cold SLOAD\n uint256 spot = _fetchSpotPrice(asset); \n MarketProtectionState storage state = assetProtectionConfig[asset];\n if (!state.isBoundedPricingEnabled) {\n _setCachedPrices(asset, spot, spot);\n return (spot, spot);\n }\n _expandPriceWindow(state, spot, asset);\n _checkAndTriggerProtection(state, spot, asset);\n (minPrice, maxPrice) = _resolveBoundedPrices(\n state.isProtectedPriceActive,\n spot,\n uint256(state.minPrice),\n uint256(state.maxPrice)\n );\n _setCachedPrices(asset, minPrice, maxPrice);\n }\n\n /**\n * @dev Expands the price window toward extremes if the spot price is a new min or max\n * @param state The market protection state\n * @param spot The current spot price\n * @param asset The underlying asset address (for event emission)\n */\n function _expandPriceWindow(MarketProtectionState storage state, uint256 spot, address asset) internal {\n uint128 spotU128 = _safeToUint128(spot);\n if (spotU128 < state.minPrice) {\n _setMinPrice(state, asset, spotU128);\n }\n if (spotU128 > state.maxPrice) {\n _setMaxPrice(state, asset, spotU128);\n }\n }\n\n /**\n * @dev Checks if the spot price has deviated beyond the threshold and triggers protection\n * @param state The market protection state\n * @param spot The current spot price\n * @param asset The underlying asset address (for event emission)\n */\n function _checkAndTriggerProtection(MarketProtectionState storage state, uint256 spot, address asset) internal {\n if (state.isProtectedPriceActive) return;\n\n if (_exceedsDeviationThreshold(spot, state.minPrice, state.maxPrice, state.triggerThreshold)) {\n state.isProtectedPriceActive = true;\n state.lastProtectionTriggeredAt = uint64(block.timestamp);\n emit ProtectionTriggered(asset, spot, state.minPrice, state.maxPrice);\n }\n }\n\n /**\n * @notice Resolves the final bounded collateral and debt prices given a spot, window bounds, and protection flag.\n * @dev When protection is active: collateral = min(spot, windowMin), debt = max(spot, windowMax).\n * When protection is inactive: both return spot.\n * @param protectionActive Whether the market protection window is currently active\n * @param spot The current spot price\n * @param windowMin The lower bound of the price window\n * @param windowMax The upper bound of the price window\n * @return minPrice The resolved lower-bound (collateral) price\n * @return maxPrice The resolved upper-bound (debt) price\n */\n function _resolveBoundedPrices(\n bool protectionActive,\n uint256 spot,\n uint256 windowMin,\n uint256 windowMax\n ) internal pure returns (uint256, uint256) {\n if (!protectionActive) return (spot, spot);\n return (spot < windowMin ? spot : windowMin, spot > windowMax ? spot : windowMax);\n }\n\n /**\n * @notice Shared view logic for all bounded price view functions.\n * Checks transient cache first for an early return; on miss, fetches from oracle\n * and computes both prices without state mutations.\n * @param vToken vToken address\n * @return minPrice The bounded lower (collateral) price\n * @return maxPrice The bounded upper (debt) price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function _computeBoundedPrices(address vToken) internal view returns (uint256 minPrice, uint256 maxPrice) {\n address asset = _getUnderlyingAsset(vToken);\n\n // Early return if both prices were cached by a prior updateProtectionState call in this tx\n (minPrice, maxPrice) = _getCachedPrices(asset);\n if (minPrice != 0 && maxPrice != 0) return (minPrice, maxPrice);\n\n // Cache miss — fetch from oracle and compute without state mutations\n uint256 spot = _fetchSpotPrice(asset);\n MarketProtectionState storage state = assetProtectionConfig[asset];\n if (!state.isBoundedPricingEnabled) return (spot, spot);\n\n // Mirror _expandPriceWindow logic: compute what the window would be after expansion\n uint128 spotU128 = _safeToUint128(spot);\n uint128 windowMin128 = spot < uint256(state.minPrice) ? spotU128 : state.minPrice;\n uint128 windowMax128 = spot > uint256(state.maxPrice) ? spotU128 : state.maxPrice;\n\n bool shouldProtect = state.isProtectedPriceActive ||\n _exceedsDeviationThreshold(spot, windowMin128, windowMax128, state.triggerThreshold);\n\n (minPrice, maxPrice) = _resolveBoundedPrices(shouldProtect, spot, uint256(windowMin128), uint256(windowMax128));\n }\n\n /**\n * @dev Computes the relative spread between the price window bounds as a ratio scaled by EXP_SCALE.\n * Formula: \\((maxPrice - minPrice) / minPrice\\), scaled by `EXP_SCALE`.\n * Used to measure how much the window has converged -- compared against `resetThreshold`\n * to determine whether the price window is tight enough to exit protection mode.\n * @param minPrice The minimum price in the window\n * @param maxPrice The maximum price in the window\n * @return The scaled bound ratio \\(((max - min) * EXP_SCALE) / min\\)\n */\n function _computePriceBoundRatio(uint128 minPrice, uint128 maxPrice) internal pure returns (uint256) {\n uint256 range = uint256(maxPrice) - uint256(minPrice);\n return (range * EXP_SCALE) / uint256(minPrice);\n }\n\n /**\n * @notice Checks if the spot price has deviated beyond the threshold from the window bounds\n * @dev Pump detection: spot > minPrice * (1 + threshold)\n * Crash detection: spot < maxPrice * (1 - threshold)\n * @param spot The current spot price\n * @param minPrice The minimum price in the window\n * @param maxPrice The maximum price in the window\n * @param threshold The deviation threshold (mantissa)\n * @return True if deviation is triggered\n */\n function _exceedsDeviationThreshold(\n uint256 spot,\n uint128 minPrice,\n uint128 maxPrice,\n uint256 threshold\n ) internal pure returns (bool) {\n uint256 upperBound = (uint256(minPrice) * (EXP_SCALE + threshold)) / EXP_SCALE;\n uint256 lowerBound = (uint256(maxPrice) * (EXP_SCALE - threshold)) / EXP_SCALE;\n return (spot > upperBound || spot < lowerBound);\n }\n\n /**\n * @dev Returns true if the relative drift between onChain and proposed exceeds KEEPER_DEADBAND\n * @param currentPrice The current on-chain price\n * @param proposedPrice The keeper's proposed price\n * @return True if drift exceeds deadband\n */\n function _exceedsCorrectionDeadband(uint128 currentPrice, uint128 proposedPrice) internal pure returns (bool) {\n if (currentPrice == 0 || proposedPrice == 0) return false;\n uint256 diff = currentPrice > proposedPrice\n ? uint256(currentPrice - proposedPrice)\n : uint256(proposedPrice - currentPrice);\n return (diff * EXP_SCALE) / uint256(currentPrice) > KEEPER_DEADBAND;\n }\n\n /**\n * @dev Sets the minimum price in the window and emits MinPriceUpdated\n * @param state The market protection state\n * @param asset The underlying asset address (for event emission)\n * @param newMin The new minimum price\n */\n function _setMinPrice(MarketProtectionState storage state, address asset, uint128 newMin) internal {\n emit MinPriceUpdated(asset, state.minPrice, newMin);\n state.minPrice = newMin;\n }\n\n /**\n * @dev Sets the maximum price in the window and emits MaxPriceUpdated\n * @param state The market protection state\n * @param asset The underlying asset address (for event emission)\n * @param newMax The new maximum price\n */\n function _setMaxPrice(MarketProtectionState storage state, address asset, uint128 newMax) internal {\n emit MaxPriceUpdated(asset, state.maxPrice, newMax);\n state.maxPrice = newMax;\n }\n\n /**\n * @dev Writes both lower and upper bounded prices to transient storage\n * @param asset The underlying asset address\n * @param minPrice The resolved lower (collateral) price to cache\n * @param maxPrice The resolved upper (debt) price to cache\n */\n function _setCachedPrices(address asset, uint256 minPrice, uint256 maxPrice) internal {\n Transient.cachePrice(COLLATERAL_PRICE_CACHE_SLOT, asset, minPrice);\n Transient.cachePrice(DEBT_PRICE_CACHE_SLOT, asset, maxPrice);\n }\n\n /**\n * @dev Reads a cached final price from transient storage\n * @param asset The underlying asset address\n * @return minPrice The cached minimum price, or 0 on cache miss\n * @return maxPrice The cached maximum price, or 0 on cache miss\n */\n function _getCachedPrices(address asset) internal view returns (uint256 minPrice, uint256 maxPrice) {\n minPrice = Transient.readCachedPrice(COLLATERAL_PRICE_CACHE_SLOT, asset);\n maxPrice = Transient.readCachedPrice(DEBT_PRICE_CACHE_SLOT, asset);\n }\n\n /**\n * @dev This function returns the underlying asset of a vToken\n * @param vToken vToken address\n * @return asset underlying asset address\n */\n function _getUnderlyingAsset(address vToken) private view returns (address asset) {\n ensureNonzeroAddress(vToken);\n if (vToken == nativeMarket) {\n asset = NATIVE_TOKEN_ADDR;\n } else if (vToken == vai) {\n asset = vai;\n } else {\n asset = VBep20Interface(vToken).underlying();\n }\n }\n\n /**\n * @dev Reverts if the market has not been initialized via setTokenConfig\n * @param asset The underlying asset address\n * @return state The market protection state storage pointer\n */\n function _ensureInitialized(address asset) internal view returns (MarketProtectionState storage state) {\n state = assetProtectionConfig[asset];\n if (state.asset == address(0)) revert MarketNotInitialized(asset);\n }\n\n /**\n * @notice Fetches the current spot price for an asset from the ResilientOracle\n * @param asset The underlying asset address\n * @return The current spot price\n */\n function _fetchSpotPrice(address asset) internal view returns (uint256) {\n return RESILIENT_ORACLE.getPrice(asset);\n }\n\n /**\n * @dev Safely casts a uint256 to uint128, reverting on overflow\n * @param value The value to cast\n * @return The value as uint128\n */\n function _safeToUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) revert PriceExceedsUint128(value);\n return uint128(value);\n }\n}\n" + }, + "contracts/hardhat-dependency-compiler/@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport '@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol';\n" + }, + "contracts/hardhat-dependency-compiler/hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport 'hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol';\n" + }, + "contracts/hardhat-dependency-compiler/hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >0.0.0;\nimport 'hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol';\n" + }, + "contracts/interfaces/FeedRegistryInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface FeedRegistryInterface {\n function latestRoundDataByName(\n string memory base,\n string memory quote\n )\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);\n\n function decimalsByName(string memory base, string memory quote) external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAccountant.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAccountant {\n function getRateSafe() external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IAnkrBNB.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAnkrBNB {\n function sharesToBonds(uint256 amount) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAsBNB.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAsBNB {\n function minter() external view returns (address);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IAsBNBMinter.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IAsBNBMinter {\n function convertToTokens(uint256 amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/ICappedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ICappedOracle {\n function updateSnapshot() external;\n}\n" + }, + "contracts/interfaces/IDeviationBoundedOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface IDeviationBoundedOracle {\n // --- Enums ---\n\n /// @notice Identifies whether a price bound is a minimum or maximum\n enum PriceBoundType {\n MIN,\n MAX\n }\n\n // --- Structs ---\n\n /// @notice Per-asset protection state tracking the min/max price window\n struct MarketProtectionState {\n /// @notice Lowest price observed in the current window (packed with maxPrice in one slot)\n uint128 minPrice;\n /// @notice Highest price observed in the current window\n uint128 maxPrice;\n /// @notice Whether protected price is currently active\n bool isProtectedPriceActive;\n /// @notice Whether this market is whitelisted for bounded pricing\n bool isBoundedPricingEnabled;\n /// @notice Timestamp of the last protection trigger — reset on every trigger\n uint64 lastProtectionTriggeredAt;\n /// @notice Minimum time protection stays active after last trigger\n uint64 cooldownPeriod;\n /// @notice The underlying asset address, used to verify initialization\n address asset;\n /// @notice Entry deviation threshold (mantissa, e.g. 0.1667e18 = 16.67%); packed with resetThreshold\n uint128 triggerThreshold;\n /// @notice Exit threshold (mantissa); window must converge below this for protection to be disabled\n uint128 resetThreshold;\n }\n\n // --- Events ---\n\n /// @notice Emitted when protection is initialized for an asset\n event ProtectionInitialized(\n address indexed asset,\n uint128 minPrice,\n uint128 maxPrice,\n uint64 cooldownPeriod,\n uint256 triggerThreshold\n );\n\n /// @notice Emitted when protection mode is triggered for an asset\n event ProtectionTriggered(address indexed asset, uint256 spotPrice, uint128 minPrice, uint128 maxPrice);\n\n /// @notice Emitted when protection mode is disabled for an asset\n event ProtectedPriceDisabled(address indexed asset);\n\n /// @notice Emitted when the keeper updates the minimum price for an asset\n event MinPriceUpdated(address indexed asset, uint128 oldMin, uint128 newMin);\n\n /// @notice Emitted when the keeper updates the maximum price for an asset\n event MaxPriceUpdated(address indexed asset, uint128 oldMax, uint128 newMax);\n\n /// @notice Emitted when the entry threshold is updated for an asset\n event TriggerThresholdSet(address indexed asset, uint256 oldThreshold, uint256 newThreshold);\n\n /// @notice Emitted when the exit threshold is updated for an asset\n event ResetThresholdSet(address indexed asset, uint256 oldExitThreshold, uint256 newExitThreshold);\n\n /// @notice Emitted when the cooldown period is updated for an asset\n event CooldownPeriodSet(address indexed asset, uint64 oldCooldown, uint64 newCooldown);\n\n /// @notice Emitted when an asset's whitelist status changes\n event BoundedPricingWhitelistUpdated(address indexed asset, bool whitelisted);\n\n // --- Errors ---\n\n /// @notice Thrown when trying to initialize protection for an asset that is not initialized\n error MarketNotInitialized(address asset);\n\n /// @notice Thrown when trying to initialize an already initialized market\n error MarketAlreadyInitialized(address asset);\n\n /// @notice Thrown when trying to disable protection that is not active\n error ProtectedPriceInactive(address asset);\n\n /// @notice Thrown when trying to disable protection before cooldown has elapsed\n error CooldownNotElapsed(address asset, uint64 lastProtectionTriggeredAt, uint64 cooldownPeriod);\n\n /// @notice Thrown when trying to disable protection before price range has converged\n error PriceRangeNotConverged(address asset, uint256 currentRangeRatio, uint256 resetThreshold);\n\n /// @notice Thrown when keeper tries to set minPrice above current spot\n error InvalidMinPrice(address asset, uint128 newMin, uint256 currentSpot);\n\n /// @notice Thrown when keeper tries to set maxPrice below current spot\n error InvalidMaxPrice(address asset, uint128 newMax, uint256 currentSpot);\n\n /// @notice Thrown when threshold is set below the minimum allowed value\n error ThresholdBelowMinimum(uint256 threshold, uint256 minimum);\n\n /// @notice Thrown when threshold is set above the maximum allowed value\n error ThresholdAboveMaximum(uint256 threshold, uint256 maximum);\n\n /// @notice Thrown when a price exceeds uint128 max\n error PriceExceedsUint128(uint256 price);\n\n /// @notice Thrown when a zero price is provided where a non-zero price is required\n error ZeroPriceNotAllowed();\n\n /// @notice Thrown when trying to initialize protection for VAI\n error VAINotAllowed();\n\n /// @notice Thrown when trying to update for an asset with active protection\n error ProtectedPriceActive(address asset);\n\n /// @notice Thrown when the lengths of the arrays are not equal\n error InvalidArrayLength();\n\n /// @notice Thrown when the exit threshold is set above the deviation threshold\n error InvalidResetThreshold(uint256 resetThreshold);\n\n // --- Non-view price functions (update window + trigger protection) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedCollateralPrice(address vToken) external returns (uint256 collateralPrice);\n\n /**\n * @notice Gets the bounded debt price for a given vToken, updating protection state\n * @param vToken vToken address\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedDebtPrice(address vToken) external returns (uint256 debtPrice);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken, updating protection state\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function getBoundedPrices(address vToken) external returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- State update (call before view price reads to populate transient cache) ---\n\n /**\n * @notice Updates the protection state for a given vToken, caching the resolved price\n * @dev Called by PolicyFacet before liquidity calculations so subsequent view price\n * reads in the same transaction are served from transient storage.\n * @param vToken vToken address\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event MinPriceUpdated if a new window minimum is recorded\n * @custom:event MaxPriceUpdated if a new window maximum is recorded\n * @custom:event ProtectionTriggered if the spot price deviates beyond the threshold\n */\n function updateProtectionState(address vToken) external;\n\n // --- View price functions (read stored/cached state only) ---\n\n /**\n * @notice Gets the bounded collateral price for a given vToken (view variant)\n * @dev Reads from transient cache first; falls back to ResilientOracle on cache miss.\n * @param vToken vToken address\n * @return price The bounded collateral price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedCollateralPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets the bounded debt price for a given vToken (view variant)\n * @dev Reads from transient cache first; falls back to ResilientOracle on cache miss.\n * @param vToken vToken address\n * @return price The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedDebtPriceView(address vToken) external view returns (uint256 price);\n\n /**\n * @notice Gets both the bounded collateral and debt prices for a given vToken (view variant)\n * @dev Reads from transient cache first; falls back to ResilientOracle on cache miss.\n * @param vToken vToken address\n * @return collateralPrice The bounded collateral price\n * @return debtPrice The bounded debt price\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128 (cache miss path only)\n */\n function getBoundedPricesView(address vToken) external view returns (uint256 collateralPrice, uint256 debtPrice);\n\n // --- Keeper functions ---\n\n /**\n * @notice Updates the minimum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMin The new minimum price; must be at or below the current spot and below maxPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMin is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMinPrice if newMin exceeds the current spot or is at or above maxPrice\n * @custom:event MinPriceUpdated\n */\n function updateMinPrice(address asset, uint128 newMin) external;\n\n /**\n * @notice Updates the maximum price in the rolling window for a given asset\n * @param asset The underlying asset address\n * @param newMax The new maximum price; must be at or above the current spot and above minPrice\n * @custom:access Only authorized keeper addresses\n * @custom:error ZeroPriceNotAllowed if newMax is zero\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error InvalidMaxPrice if newMax is below the current spot or is at or below minPrice\n * @custom:event MaxPriceUpdated\n */\n function updateMaxPrice(address asset, uint128 newMax) external;\n\n /**\n * @notice Disables protection mode for a given asset once conditions are met\n * @param asset The underlying asset address\n * @custom:access Only authorized monitor/keeper addresses\n * @custom:error ProtectedPriceInactive if protection is not currently active\n * @custom:error CooldownNotElapsed if the cooldown period has not elapsed since the last trigger\n * @custom:error PriceRangeNotConverged if the window range is still above the exit threshold\n * @custom:event ProtectedPriceDisabled\n */\n function disableActiveProtection(address asset) external;\n\n // --- Admin functions (governance-gated) ---\n\n /**\n * @notice Initializes protection parameters for a new asset and whitelists it\n * @dev Seeds the initial min/max window from the current ResilientOracle spot price,\n * confirming the oracle is live for this asset before it is listed.\n * @param asset The underlying asset address\n * @param cooldownPeriod Minimum time protection stays active after the last trigger, in seconds\n * @param triggerThreshold Deviation threshold that activates protection (mantissa). Must be between 5% and 50%.\n * @param resetThreshold Deviation threshold below which protection can be exited (mantissa). Must be non-zero and below triggerThreshold.\n * @custom:access Only Governance\n * @custom:error MarketAlreadyInitialized if the asset has already been initialized\n * @custom:error ThresholdBelowMinimum if triggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if triggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if resetThreshold is at or above triggerThreshold\n * @custom:error VAINotAllowed if asset is the VAI token\n * @custom:error PriceExceedsUint128 if the spot price overflows uint128\n * @custom:event ProtectionInitialized\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setTokenConfig(\n address asset,\n uint64 cooldownPeriod,\n uint256 triggerThreshold,\n uint256 resetThreshold\n ) external;\n\n /**\n * @notice Sets the cooldown period for an asset\n * @param asset The underlying asset address\n * @param newCooldown The new cooldown period in seconds; must be non-zero\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:event CooldownPeriodSet\n */\n function setCooldownPeriod(address asset, uint64 newCooldown) external;\n\n /**\n * @notice Sets the trigger and reset thresholds for an asset\n * @param asset The underlying asset address\n * @param newTriggerThreshold The new trigger threshold (mantissa). Must be between 5% and 50% and above the reset threshold.\n * @param newResetThreshold The new reset threshold (mantissa). Must be non-zero and below the trigger threshold.\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ThresholdBelowMinimum if newTriggerThreshold is below 5%\n * @custom:error ThresholdAboveMaximum if newTriggerThreshold is above 50%\n * @custom:error InvalidResetThreshold if newResetThreshold is at or above newTriggerThreshold\n * @custom:event TriggerThresholdSet if the trigger threshold changed\n * @custom:event ResetThresholdSet if the reset threshold changed\n */\n function setThresholds(address asset, uint256 newTriggerThreshold, uint256 newResetThreshold) external;\n\n /**\n * @notice Sets whether bounded pricing is enabled for an asset\n * @param asset The underlying asset address\n * @param enabled Whether bounded pricing should be enabled for the asset\n * @custom:access Only Governance\n * @custom:error MarketNotInitialized if the asset has not been initialized\n * @custom:error ProtectedPriceActive if trying to disable an asset while protection is active\n * @custom:event BoundedPricingWhitelistUpdated\n */\n function setAssetBoundedPricingEnabled(address asset, bool enabled) external;\n\n // --- View helpers ---\n\n /**\n * @notice Returns the full protection state for an asset\n * @param asset The underlying asset address\n * @return minPrice Lowest price observed in the current window\n * @return maxPrice Highest price observed in the current window\n * @return isProtectedPriceActive Whether protected price is currently active\n * @return isBoundedPricingEnabled Whether the asset is whitelisted for bounded pricing\n * @return lastProtectionTriggeredAt Timestamp of the last protection trigger\n * @return cooldownPeriod Minimum time protection stays active after last trigger\n * @return assetAddr The underlying asset address stored in the struct\n * @return triggerThreshold Entry deviation threshold (mantissa) that activates protection\n * @return resetThreshold Exit deviation threshold (mantissa) below which protection can be disabled\n */\n function assetProtectionConfig(\n address asset\n )\n external\n view\n returns (\n uint128 minPrice,\n uint128 maxPrice,\n bool isProtectedPriceActive,\n bool isBoundedPricingEnabled,\n uint64 lastProtectionTriggeredAt,\n uint64 cooldownPeriod,\n address assetAddr,\n uint128 triggerThreshold,\n uint128 resetThreshold\n );\n\n /**\n * @notice Checks if an asset is whitelisted for bounded pricing\n * @param asset The underlying asset address\n * @return True if the asset is whitelisted\n */\n function isBoundedPricingEnabled(address asset) external view returns (bool);\n\n /**\n * @notice Checks if protection is currently active for an asset\n * @param asset The underlying asset address\n * @return True if protected price is active\n */\n function isProtectedPriceActive(address asset) external view returns (bool);\n\n /**\n * @notice Checks if protection can be exited for a given asset\n * @param asset The underlying asset address\n * @return True if both the cooldown has elapsed and the price range has converged below the exit threshold\n */\n function canExitProtection(address asset) external view returns (bool);\n\n /**\n * @notice Returns the initialized asset at the given index (auto-generated array getter)\n * @param index Array index\n * @return The asset address at the given index\n */\n function allAssets(uint256 index) external view returns (address);\n\n /**\n * @notice Returns all currently whitelisted asset addresses\n * @return result Array of whitelisted asset addresses\n */\n function getAllBoundedPricingEnabledAssets() external view returns (address[] memory result);\n\n /**\n * @notice Returns all asset addresses that have ever been initialized\n * @return Array of all initialized asset addresses\n */\n function getInitializedAssets() external view returns (address[] memory);\n\n /**\n * @notice Batch-checks which assets' on-chain min/max have drifted beyond the keeper deadband\n * @param assets Array of asset addresses to check\n * @param proposedMins Keeper's proposed window minimum prices\n * @param proposedMaxs Keeper's proposed window maximum prices\n * @return needsMinUpdate Whether minPrice drift exceeds the deadband for each asset\n * @return needsMaxUpdate Whether maxPrice drift exceeds the deadband for each asset\n * @custom:error InvalidArrayLength if the input array lengths do not match\n */\n function checkAndGetWindowDrift(\n address[] calldata assets,\n uint128[] calldata proposedMins,\n uint128[] calldata proposedMaxs\n ) external view returns (bool[] memory needsMinUpdate, bool[] memory needsMaxUpdate);\n}\n" + }, + "contracts/interfaces/IERC4626.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IERC4626 {\n function convertToAssets(uint256 shares) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IEtherFiLiquidityPool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IEtherFiLiquidityPool {\n function amountForShare(uint256 _share) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IPendlePtOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IPendlePtOracle {\n function getPtToAssetRate(address market, uint32 duration) external view returns (uint256);\n\n function getPtToSyRate(address market, uint32 duration) external view returns (uint256);\n\n function getOracleState(\n address market,\n uint32 duration\n )\n external\n view\n returns (bool increaseCardinalityRequired, uint16 cardinalityRequired, bool oldestObservationSatisfied);\n}\n" + }, + "contracts/interfaces/IPStakePool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IPStakePool {\n struct Data {\n uint256 totalWei;\n uint256 poolTokenSupply;\n }\n\n /**\n * @dev The current exchange rate for converting stkBNB to BNB.\n */\n function exchangeRate() external view returns (Data memory);\n}\n" + }, + "contracts/interfaces/ISFrax.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISFrax {\n function convertToAssets(uint256 shares) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/ISfrxEthFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISfrxEthFraxOracle {\n function getPrices() external view returns (bool _isbadData, uint256 _priceLow, uint256 _priceHigh);\n}\n" + }, + "contracts/interfaces/IStaderStakeManager.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IStaderStakeManager {\n function convertBnbXToBnb(uint256 _amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IStETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface IStETH {\n function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/ISynclubStakeManager.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface ISynclubStakeManager {\n function convertSnBnbToBnb(uint256 _amount) external view returns (uint256);\n}\n" + }, + "contracts/interfaces/IWBETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\ninterface IWBETH {\n function exchangeRate() external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/IZkETH.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface IZkETH {\n function LSTPerToken() external view returns (uint256);\n function decimals() external view returns (uint8);\n}\n" + }, + "contracts/interfaces/OracleInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\ninterface OracleInterface {\n function getPrice(address asset) external view returns (uint256);\n}\n\ninterface ResilientOracleInterface is OracleInterface {\n function updatePrice(address vToken) external;\n\n function updateAssetPrice(address asset) external;\n\n function getUnderlyingPrice(address vToken) external view returns (uint256);\n}\n\ninterface BoundValidatorInterface {\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool);\n}\n" + }, + "contracts/interfaces/PublicResolverInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity ^0.8.25;\n\ninterface PublicResolverInterface {\n function addr(bytes32 node) external view returns (address payable);\n}\n" + }, + "contracts/interfaces/SIDRegistryInterface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity ^0.8.25;\n\ninterface SIDRegistryInterface {\n function resolver(bytes32 node) external view returns (address);\n}\n" + }, + "contracts/interfaces/VBep20Interface.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\ninterface VBep20Interface is IERC20Metadata {\n /**\n * @notice Underlying asset for this VToken\n */\n function underlying() external view returns (address);\n}\n" + }, + "contracts/lib/Transient.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity ^0.8.25;\n\nlibrary Transient {\n /**\n * @notice Cache the asset price into transient storage\n * @param key address of the asset\n * @param value asset price\n */\n function cachePrice(bytes32 cacheSlot, address key, uint256 value) internal {\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\n assembly (\"memory-safe\") {\n tstore(slot, value)\n }\n }\n\n /**\n * @notice Read cached price from transient storage\n * @param key address of the asset\n * @return value cached asset price\n */\n function readCachedPrice(bytes32 cacheSlot, address key) internal view returns (uint256 value) {\n bytes32 slot = keccak256(abi.encode(cacheSlot, key));\n assembly (\"memory-safe\") {\n value := tload(slot)\n }\n }\n}\n" + }, + "contracts/oracles/AnkrBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IAnkrBNB } from \"../interfaces/IAnkrBNB.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title AnkrBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of ankrBNB asset\n */\ncontract AnkrBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address ankrBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n ankrBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of BNB for 1 ankrBNB\n * @return amount The amount of BNB for ankrBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IAnkrBNB(CORRELATED_TOKEN).sharesToBonds(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/AsBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IAsBNB } from \"../interfaces/IAsBNB.sol\";\nimport { IAsBNBMinter } from \"../interfaces/IAsBNBMinter.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title asBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of asBNB asset\n */\ncontract AsBNBOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address asBNB,\n address slisBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n asBNB,\n slisBNB,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of slisBNB for 1 asBNB\n * @return price The amount of slisBNB for asBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n IAsBNBMinter minter = IAsBNBMinter(IAsBNB(CORRELATED_TOKEN).minter());\n return minter.convertToTokens(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/BinanceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/SIDRegistryInterface.sol\";\nimport \"../interfaces/FeedRegistryInterface.sol\";\nimport \"../interfaces/PublicResolverInterface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title BinanceOracle\n * @author Venus\n * @notice This oracle fetches price of assets from Binance.\n */\ncontract BinanceOracle is AccessControlledV8, OracleInterface {\n /// @notice Used to fetch feed registry address.\n address public sidRegistryAddress;\n\n /// @notice Set this as asset address for BNB. This is the underlying address for vBNB\n address public constant BNB_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Max stale period configuration for assets\n mapping(string => uint256) public maxStalePeriod;\n\n /// @notice Override symbols to be compatible with Binance feed registry\n mapping(string => string) public symbols;\n\n /// @notice Used to fetch price of assets used directly when space ID is not supported by current chain.\n address public feedRegistryAddress;\n\n /// @notice Emits when asset stale period is updated.\n event MaxStalePeriodAdded(string indexed asset, uint256 maxStalePeriod);\n\n /// @notice Emits when symbol of the asset is updated.\n event SymbolOverridden(string indexed symbol, string overriddenSymbol);\n\n /// @notice Emits when address of feed registry is updated.\n event FeedRegistryUpdated(address indexed oldFeedRegistry, address indexed newFeedRegistry);\n\n /**\n * @notice Checks whether an address is null or not\n */\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Sets the contracts required to fetch prices\n * @param _sidRegistryAddress Address of SID registry\n * @param _acm Address of the access control manager contract\n */\n function initialize(address _sidRegistryAddress, address _acm) external initializer {\n sidRegistryAddress = _sidRegistryAddress;\n __AccessControlled_init(_acm);\n }\n\n /**\n * @notice Used to set the max stale period of an asset\n * @param symbol The symbol of the asset\n * @param _maxStalePeriod The max stake period\n */\n function setMaxStalePeriod(string memory symbol, uint256 _maxStalePeriod) external {\n _checkAccessAllowed(\"setMaxStalePeriod(string,uint256)\");\n if (_maxStalePeriod == 0) revert(\"stale period can't be zero\");\n if (bytes(symbol).length == 0) revert(\"symbol cannot be empty\");\n\n maxStalePeriod[symbol] = _maxStalePeriod;\n emit MaxStalePeriodAdded(symbol, _maxStalePeriod);\n }\n\n /**\n * @notice Used to override a symbol when fetching price\n * @param symbol The symbol to override\n * @param overrideSymbol The symbol after override\n */\n function setSymbolOverride(string calldata symbol, string calldata overrideSymbol) external {\n _checkAccessAllowed(\"setSymbolOverride(string,string)\");\n if (bytes(symbol).length == 0) revert(\"symbol cannot be empty\");\n\n symbols[symbol] = overrideSymbol;\n emit SymbolOverridden(symbol, overrideSymbol);\n }\n\n /**\n * @notice Used to set feed registry address when current chain does not support space ID.\n * @param newfeedRegistryAddress Address of new feed registry.\n */\n function setFeedRegistryAddress(\n address newfeedRegistryAddress\n ) external notNullAddress(newfeedRegistryAddress) onlyOwner {\n if (sidRegistryAddress != address(0)) revert(\"sidRegistryAddress must be zero\");\n emit FeedRegistryUpdated(feedRegistryAddress, newfeedRegistryAddress);\n feedRegistryAddress = newfeedRegistryAddress;\n }\n\n /**\n * @notice Uses Space ID to fetch the feed registry address\n * @return feedRegistryAddress Address of binance oracle feed registry.\n */\n function getFeedRegistryAddress() public view returns (address) {\n bytes32 nodeHash = 0x94fe3821e0768eb35012484db4df61890f9a6ca5bfa984ef8ff717e73139faff;\n\n SIDRegistryInterface sidRegistry = SIDRegistryInterface(sidRegistryAddress);\n address publicResolverAddress = sidRegistry.resolver(nodeHash);\n PublicResolverInterface publicResolver = PublicResolverInterface(publicResolverAddress);\n\n return publicResolver.addr(nodeHash);\n }\n\n /**\n * @notice Gets the price of a asset from the binance oracle\n * @param asset Address of the asset\n * @return Price in USD\n */\n function getPrice(address asset) public view returns (uint256) {\n string memory symbol;\n uint256 decimals;\n\n if (asset == BNB_ADDR) {\n symbol = \"BNB\";\n decimals = 18;\n } else {\n IERC20Metadata token = IERC20Metadata(asset);\n symbol = token.symbol();\n decimals = token.decimals();\n }\n\n string memory overrideSymbol = symbols[symbol];\n\n if (bytes(overrideSymbol).length != 0) {\n symbol = overrideSymbol;\n }\n\n return _getPrice(symbol, decimals);\n }\n\n function _getPrice(string memory symbol, uint256 decimals) internal view returns (uint256) {\n FeedRegistryInterface feedRegistry;\n\n if (sidRegistryAddress != address(0)) {\n // If sidRegistryAddress is available, fetch feedRegistryAddress from sidRegistry\n feedRegistry = FeedRegistryInterface(getFeedRegistryAddress());\n } else {\n // Use feedRegistry directly if sidRegistryAddress is not available\n feedRegistry = FeedRegistryInterface(feedRegistryAddress);\n }\n\n (, int256 answer, , uint256 updatedAt, ) = feedRegistry.latestRoundDataByName(symbol, \"USD\");\n if (answer <= 0) revert(\"invalid binance oracle price\");\n if (block.timestamp < updatedAt) revert(\"updatedAt exceeds block time\");\n\n uint256 deltaTime;\n unchecked {\n deltaTime = block.timestamp - updatedAt;\n }\n if (deltaTime > maxStalePeriod[symbol]) revert(\"binance oracle price expired\");\n\n uint256 decimalDelta = feedRegistry.decimalsByName(symbol, \"USD\");\n return (uint256(answer) * (10 ** (18 - decimalDelta))) * (10 ** (18 - decimals));\n }\n}\n" + }, + "contracts/oracles/BNBxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IStaderStakeManager } from \"../interfaces/IStaderStakeManager.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title BNBxOracle\n * @author Venus\n * @notice This oracle fetches the price of BNBx asset\n */\ncontract BNBxOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakeManager\n IStaderStakeManager public immutable STAKE_MANAGER;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakeManager,\n address bnbx,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n bnbx,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakeManager);\n STAKE_MANAGER = IStaderStakeManager(stakeManager);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 BNBx\n * @return price The amount of BNB for BNBx\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STAKE_MANAGER.convertBnbXToBnb(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/BoundValidator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\n\n/**\n * @title BoundValidator\n * @author Venus\n * @notice The BoundValidator contract is used to validate prices fetched from two different sources.\n * Each asset has an upper and lower bound ratio set in the config. In order for a price to be valid\n * it must fall within this range of the validator price.\n */\ncontract BoundValidator is AccessControlledV8, BoundValidatorInterface {\n struct ValidateConfig {\n /// @notice asset address\n address asset;\n /// @notice Upper bound of deviation between reported price and anchor price,\n /// beyond which the reported price will be invalidated\n uint256 upperBoundRatio;\n /// @notice Lower bound of deviation between reported price and anchor price,\n /// below which the reported price will be invalidated\n uint256 lowerBoundRatio;\n }\n\n /// @notice validation configs by asset\n mapping(address => ValidateConfig) public validateConfigs;\n\n /// @notice Emit this event when new validation configs are added\n event ValidateConfigAdded(address indexed asset, uint256 indexed upperBound, uint256 indexed lowerBound);\n\n /// @notice Constructor for the implementation contract. Sets immutable variables.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the owner of the contract\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n /**\n * @notice Add multiple validation configs at the same time\n * @param configs Array of validation configs\n * @custom:access Only Governance\n * @custom:error Zero length error is thrown if length of the config array is 0\n * @custom:event Emits ValidateConfigAdded for each validation config that is successfully set\n */\n function setValidateConfigs(ValidateConfig[] memory configs) external {\n uint256 length = configs.length;\n if (length == 0) revert(\"invalid validate config length\");\n for (uint256 i; i < length; ++i) {\n setValidateConfig(configs[i]);\n }\n }\n\n /**\n * @notice Add a single validation config\n * @param config Validation config struct\n * @custom:access Only Governance\n * @custom:error Null address error is thrown if asset address is null\n * @custom:error Range error thrown if bound ratio is not positive\n * @custom:error Range error thrown if lower bound is greater than or equal to upper bound\n * @custom:event Emits ValidateConfigAdded when a validation config is successfully set\n */\n function setValidateConfig(ValidateConfig memory config) public {\n _checkAccessAllowed(\"setValidateConfig(ValidateConfig)\");\n\n if (config.asset == address(0)) revert(\"asset can't be zero address\");\n if (config.upperBoundRatio == 0 || config.lowerBoundRatio == 0) revert(\"bound must be positive\");\n if (config.upperBoundRatio <= config.lowerBoundRatio) revert(\"upper bound must be higher than lowner bound\");\n validateConfigs[config.asset] = config;\n emit ValidateConfigAdded(config.asset, config.upperBoundRatio, config.lowerBoundRatio);\n }\n\n /**\n * @notice Test reported asset price against anchor price\n * @param asset asset address\n * @param reportedPrice The price to be tested\n * @custom:error Missing error thrown if asset config is not set\n * @custom:error Price error thrown if anchor price is not valid\n */\n function validatePriceWithAnchorPrice(\n address asset,\n uint256 reportedPrice,\n uint256 anchorPrice\n ) public view virtual override returns (bool) {\n if (validateConfigs[asset].upperBoundRatio == 0) revert(\"validation config not exist\");\n if (anchorPrice == 0) revert(\"anchor price is not valid\");\n return _isWithinAnchor(asset, reportedPrice, anchorPrice);\n }\n\n /**\n * @notice Test whether the reported price is within the valid bounds\n * @param asset Asset address\n * @param reportedPrice The price to be tested\n * @param anchorPrice The reported price must be within the the valid bounds of this price\n */\n function _isWithinAnchor(address asset, uint256 reportedPrice, uint256 anchorPrice) private view returns (bool) {\n if (reportedPrice != 0) {\n // we need to multiply anchorPrice by 1e18 to make the ratio 18 decimals\n uint256 anchorRatio = (anchorPrice * 1e18) / reportedPrice;\n uint256 upperBoundAnchorRatio = validateConfigs[asset].upperBoundRatio;\n uint256 lowerBoundAnchorRatio = validateConfigs[asset].lowerBoundRatio;\n return anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio;\n }\n return false;\n }\n\n // BoundValidator is to get inherited, so it's a good practice to add some storage gaps like\n // OpenZepplin proposed in their contracts: https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n // solhint-disable-next-line\n uint256[49] private __gap;\n}\n" + }, + "contracts/oracles/ChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/VBep20Interface.sol\";\nimport \"../interfaces/OracleInterface.sol\";\nimport \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\nimport \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\n\n/**\n * @title ChainlinkOracle\n * @author Venus\n * @notice This oracle fetches prices of assets from the Chainlink oracle.\n */\ncontract ChainlinkOracle is AccessControlledV8, OracleInterface {\n struct TokenConfig {\n /// @notice Underlying token address, which can't be a null address\n /// @notice Used to check if a token is supported\n /// @notice 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB address for native tokens\n /// (e.g BNB for BNB chain, ETH for Ethereum network)\n address asset;\n /// @notice Chainlink feed address\n address feed;\n /// @notice Price expiration period of this asset\n uint256 maxStalePeriod;\n }\n\n /// @notice Set this as asset address for native token on each chain.\n /// This is the underlying address for vBNB on BNB chain or an underlying asset for a native market on any chain.\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Manually set an override price, useful under extenuating conditions such as price feed failure\n mapping(address => uint256) public prices;\n\n /// @notice Token config by assets\n mapping(address => TokenConfig) public tokenConfigs;\n\n /// @notice Emit when a price is manually set\n event PricePosted(address indexed asset, uint256 previousPriceMantissa, uint256 newPriceMantissa);\n\n /// @notice Emit when a token config is added\n event TokenConfigAdded(address indexed asset, address feed, uint256 maxStalePeriod);\n\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor() {\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the owner of the contract\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n }\n\n /**\n * @notice Manually set the price of a given asset\n * @param asset Asset address\n * @param price Asset price in 18 decimals\n * @custom:access Only Governance\n * @custom:event Emits PricePosted event on successfully setup of asset price\n */\n function setDirectPrice(address asset, uint256 price) external notNullAddress(asset) {\n _checkAccessAllowed(\"setDirectPrice(address,uint256)\");\n\n uint256 previousPriceMantissa = prices[asset];\n prices[asset] = price;\n emit PricePosted(asset, previousPriceMantissa, price);\n }\n\n /**\n * @notice Add multiple token configs at the same time\n * @param tokenConfigs_ config array\n * @custom:access Only Governance\n * @custom:error Zero length error thrown, if length of the array in parameter is 0\n */\n function setTokenConfigs(TokenConfig[] memory tokenConfigs_) external {\n if (tokenConfigs_.length == 0) revert(\"length can't be 0\");\n uint256 numTokenConfigs = tokenConfigs_.length;\n for (uint256 i; i < numTokenConfigs; ++i) {\n setTokenConfig(tokenConfigs_[i]);\n }\n }\n\n /**\n * @notice Add single token config. asset & feed cannot be null addresses and maxStalePeriod must be positive\n * @param tokenConfig Token config struct\n * @custom:access Only Governance\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error NotNullAddress error is thrown if token feed address is null\n * @custom:error Range error is thrown if maxStale period of token is not greater than zero\n * @custom:event Emits TokenConfigAdded event on successfully setting of the token config\n */\n function setTokenConfig(\n TokenConfig memory tokenConfig\n ) public notNullAddress(tokenConfig.asset) notNullAddress(tokenConfig.feed) {\n _checkAccessAllowed(\"setTokenConfig(TokenConfig)\");\n\n if (tokenConfig.maxStalePeriod == 0) revert(\"stale period can't be zero\");\n tokenConfigs[tokenConfig.asset] = tokenConfig;\n emit TokenConfigAdded(tokenConfig.asset, tokenConfig.feed, tokenConfig.maxStalePeriod);\n }\n\n /**\n * @notice Gets the price of a asset from the chainlink oracle\n * @param asset Address of the asset\n * @return Price in USD from Chainlink or a manually set price for the asset\n */\n function getPrice(address asset) public view virtual returns (uint256) {\n uint256 decimals;\n\n if (asset == NATIVE_TOKEN_ADDR) {\n decimals = 18;\n } else {\n IERC20Metadata token = IERC20Metadata(asset);\n decimals = token.decimals();\n }\n\n return _getPriceInternal(asset, decimals);\n }\n\n /**\n * @notice Gets the Chainlink price for a given asset\n * @param asset address of the asset\n * @param decimals decimals of the asset\n * @return price Asset price in USD or a manually set price of the asset\n */\n function _getPriceInternal(address asset, uint256 decimals) internal view returns (uint256 price) {\n uint256 tokenPrice = prices[asset];\n if (tokenPrice != 0) {\n price = tokenPrice;\n } else {\n price = _getChainlinkPrice(asset);\n }\n\n uint256 decimalDelta = 18 - decimals;\n return price * (10 ** decimalDelta);\n }\n\n /**\n * @notice Get the Chainlink price for an asset, revert if token config doesn't exist\n * @dev The precision of the price feed is used to ensure the returned price has 18 decimals of precision\n * @param asset Address of the asset\n * @return price Price in USD, with 18 decimals of precision\n * @custom:error NotNullAddress error is thrown if the asset address is null\n * @custom:error Price error is thrown if the Chainlink price of asset is not greater than zero\n * @custom:error Timing error is thrown if current timestamp is less than the last updatedAt timestamp\n * @custom:error Timing error is thrown if time difference between current time and last updated time\n * is greater than maxStalePeriod\n */\n function _getChainlinkPrice(\n address asset\n ) private view notNullAddress(tokenConfigs[asset].asset) returns (uint256) {\n TokenConfig memory tokenConfig = tokenConfigs[asset];\n AggregatorV3Interface feed = AggregatorV3Interface(tokenConfig.feed);\n\n // note: maxStalePeriod cannot be 0\n uint256 maxStalePeriod = tokenConfig.maxStalePeriod;\n\n // Chainlink USD-denominated feeds store answers at 8 decimals, mostly\n uint256 decimalDelta = 18 - feed.decimals();\n\n (, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();\n if (answer <= 0) revert(\"chainlink price must be positive\");\n if (block.timestamp < updatedAt) revert(\"updatedAt exceeds block time\");\n\n uint256 deltaTime;\n unchecked {\n deltaTime = block.timestamp - updatedAt;\n }\n\n if (deltaTime > maxStalePeriod) revert(\"chainlink price expired\");\n\n return uint256(answer) * (10 ** decimalDelta);\n }\n}\n" + }, + "contracts/oracles/common/CorrelatedTokenOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OracleInterface, ResilientOracleInterface } from \"../../interfaces/OracleInterface.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { SECONDS_PER_YEAR } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport { ICappedOracle } from \"../../interfaces/ICappedOracle.sol\";\nimport { IAccessControlManagerV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/IAccessControlManagerV8.sol\";\n\n/**\n * @title CorrelatedTokenOracle\n * @notice This oracle fetches the price of a token that is correlated to another token.\n */\nabstract contract CorrelatedTokenOracle is OracleInterface, ICappedOracle {\n /// @notice Address of the correlated token\n address public immutable CORRELATED_TOKEN;\n\n /// @notice Address of the underlying token\n address public immutable UNDERLYING_TOKEN;\n\n /// @notice Address of Resilient Oracle\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Address of the AccessControlManager contract\n IAccessControlManagerV8 public immutable ACCESS_CONTROL_MANAGER;\n\n //// @notice Growth rate percentage in seconds. Ex: 1e18 is 100%\n uint256 public growthRatePerSecond;\n\n /// @notice Snapshot update interval\n uint256 public snapshotInterval;\n\n /// @notice Last stored snapshot maximum exchange rate\n uint256 public snapshotMaxExchangeRate;\n\n /// @notice Last stored snapshot timestamp\n uint256 public snapshotTimestamp;\n\n /// @notice Gap to add when updating the snapshot\n uint256 public snapshotGap;\n\n /// @notice Emitted when the snapshot is updated\n event SnapshotUpdated(uint256 indexed maxExchangeRate, uint256 indexed timestamp);\n\n /// @notice Emitted when the growth rate is updated\n event GrowthRateUpdated(\n uint256 indexed oldGrowthRatePerSecond,\n uint256 indexed newGrowthRatePerSecond,\n uint256 indexed oldSnapshotInterval,\n uint256 newSnapshotInterval\n );\n\n /// @notice Emitted when the snapshot gap is updated\n event SnapshotGapUpdated(uint256 indexed oldSnapshotGap, uint256 indexed newSnapshotGap);\n\n /// @notice Thrown if the token address is invalid\n error InvalidTokenAddress();\n\n /// @notice Thrown if the growth rate is invalid\n error InvalidGrowthRate();\n\n /// @notice Thrown if the initial snapshot is invalid\n error InvalidInitialSnapshot();\n\n /// @notice Thrown if the max snapshot exchange rate is invalid\n error InvalidSnapshotMaxExchangeRate();\n\n /// @notice @notice Thrown when the action is prohibited by AccessControlManager\n error Unauthorized(address sender, address calledContract, string methodSignature);\n\n /**\n * @notice Constructor for the implementation contract.\n * @custom:error InvalidGrowthRate error is thrown if the growth rate is invalid\n * @custom:error InvalidInitialSnapshot error is thrown if the initial snapshot values are invalid\n */\n constructor(\n address _correlatedToken,\n address _underlyingToken,\n address _resilientOracle,\n uint256 _annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 _initialSnapshotMaxExchangeRate,\n uint256 _initialSnapshotTimestamp,\n address _accessControlManager,\n uint256 _snapshotGap\n ) {\n growthRatePerSecond = _annualGrowthRate / SECONDS_PER_YEAR;\n\n if ((growthRatePerSecond == 0 && _snapshotInterval > 0) || (growthRatePerSecond > 0 && _snapshotInterval == 0))\n revert InvalidGrowthRate();\n\n if ((_initialSnapshotMaxExchangeRate == 0 || _initialSnapshotTimestamp == 0) && _snapshotInterval > 0) {\n revert InvalidInitialSnapshot();\n }\n\n ensureNonzeroAddress(_correlatedToken);\n ensureNonzeroAddress(_underlyingToken);\n ensureNonzeroAddress(_resilientOracle);\n ensureNonzeroAddress(_accessControlManager);\n\n CORRELATED_TOKEN = _correlatedToken;\n UNDERLYING_TOKEN = _underlyingToken;\n RESILIENT_ORACLE = ResilientOracleInterface(_resilientOracle);\n snapshotInterval = _snapshotInterval;\n\n snapshotMaxExchangeRate = _initialSnapshotMaxExchangeRate;\n snapshotTimestamp = _initialSnapshotTimestamp;\n snapshotGap = _snapshotGap;\n\n ACCESS_CONTROL_MANAGER = IAccessControlManagerV8(_accessControlManager);\n }\n\n /**\n * @notice Directly sets the snapshot exchange rate and timestamp\n * @param _snapshotMaxExchangeRate The exchange rate to set\n * @param _snapshotTimestamp The timestamp to set\n * @custom:event Emits SnapshotUpdated event on successful update of the snapshot\n */\n function setSnapshot(uint256 _snapshotMaxExchangeRate, uint256 _snapshotTimestamp) external {\n _checkAccessAllowed(\"setSnapshot(uint256,uint256)\");\n\n snapshotMaxExchangeRate = _snapshotMaxExchangeRate;\n snapshotTimestamp = _snapshotTimestamp;\n\n emit SnapshotUpdated(snapshotMaxExchangeRate, snapshotTimestamp);\n }\n\n /**\n * @notice Sets the growth rate and snapshot interval\n * @param _annualGrowthRate The annual growth rate to set\n * @param _snapshotInterval The snapshot interval to set\n * @custom:error InvalidGrowthRate error is thrown if the growth rate is invalid\n * @custom:event Emits GrowthRateUpdated event on successful update of the growth rate\n */\n function setGrowthRate(uint256 _annualGrowthRate, uint256 _snapshotInterval) external {\n _checkAccessAllowed(\"setGrowthRate(uint256,uint256)\");\n uint256 oldGrowthRatePerSecond = growthRatePerSecond;\n\n growthRatePerSecond = _annualGrowthRate / SECONDS_PER_YEAR;\n\n if ((growthRatePerSecond == 0 && _snapshotInterval > 0) || (growthRatePerSecond > 0 && _snapshotInterval == 0))\n revert InvalidGrowthRate();\n\n emit GrowthRateUpdated(oldGrowthRatePerSecond, growthRatePerSecond, snapshotInterval, _snapshotInterval);\n\n snapshotInterval = _snapshotInterval;\n }\n\n /**\n * @notice Sets the snapshot gap\n * @param _snapshotGap The snapshot gap to set\n * @custom:event Emits SnapshotGapUpdated event on successful update of the snapshot gap\n */\n function setSnapshotGap(uint256 _snapshotGap) external {\n _checkAccessAllowed(\"setSnapshotGap(uint256)\");\n\n emit SnapshotGapUpdated(snapshotGap, _snapshotGap);\n\n snapshotGap = _snapshotGap;\n }\n\n /**\n * @notice Returns if the price is capped\n * @return isCapped Boolean indicating if the price is capped\n */\n function isCapped() external view virtual returns (bool) {\n if (snapshotInterval == 0) {\n return false;\n }\n\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n if (maxAllowedExchangeRate == 0) {\n return false;\n }\n\n uint256 exchangeRate = getUnderlyingAmount();\n\n return exchangeRate > maxAllowedExchangeRate;\n }\n\n /**\n * @notice Updates the snapshot price and timestamp\n * @custom:event Emits SnapshotUpdated event on successful update of the snapshot\n * @custom:error InvalidSnapshotMaxExchangeRate error is thrown if the max snapshot exchange rate is zero\n */\n function updateSnapshot() public override {\n if (block.timestamp - snapshotTimestamp < snapshotInterval || snapshotInterval == 0) return;\n\n uint256 exchangeRate = getUnderlyingAmount();\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n\n snapshotMaxExchangeRate =\n (exchangeRate > maxAllowedExchangeRate ? maxAllowedExchangeRate : exchangeRate) +\n snapshotGap;\n snapshotTimestamp = block.timestamp;\n\n if (snapshotMaxExchangeRate == 0) revert InvalidSnapshotMaxExchangeRate();\n\n RESILIENT_ORACLE.updateAssetPrice(UNDERLYING_TOKEN);\n emit SnapshotUpdated(snapshotMaxExchangeRate, snapshotTimestamp);\n }\n\n /**\n * @notice Fetches the price of the token\n * @param asset Address of the token\n * @return price The price of the token in scaled decimal places. It can be capped\n * to a maximum value taking into account the growth rate\n * @custom:error InvalidTokenAddress error is thrown if the token address is invalid\n */\n function getPrice(address asset) public view override returns (uint256) {\n if (asset != CORRELATED_TOKEN) revert InvalidTokenAddress();\n\n uint256 exchangeRate = getUnderlyingAmount();\n\n if (snapshotInterval == 0) {\n return _calculatePrice(exchangeRate);\n }\n\n uint256 maxAllowedExchangeRate = getMaxAllowedExchangeRate();\n\n uint256 finalExchangeRate = (exchangeRate > maxAllowedExchangeRate && maxAllowedExchangeRate != 0)\n ? maxAllowedExchangeRate\n : exchangeRate;\n\n return _calculatePrice(finalExchangeRate);\n }\n\n /**\n * @notice Gets the maximum allowed exchange rate for token\n * @return maxExchangeRate Maximum allowed exchange rate\n */\n function getMaxAllowedExchangeRate() public view returns (uint256) {\n uint256 timeElapsed = block.timestamp - snapshotTimestamp;\n uint256 maxExchangeRate = snapshotMaxExchangeRate +\n (snapshotMaxExchangeRate * growthRatePerSecond * timeElapsed) /\n 1e18;\n return maxExchangeRate;\n }\n\n /**\n * @notice Gets the underlying amount for correlated token\n * @return underlyingAmount Amount of underlying token\n */\n function getUnderlyingAmount() public view virtual returns (uint256);\n\n /**\n * @notice Fetches price of the token based on an underlying exchange rate\n * @param exchangeRate The underlying exchange rate to use\n * @return price The price of the token in scaled decimal places\n */\n function _calculatePrice(uint256 exchangeRate) internal view returns (uint256) {\n uint256 underlyingUSDPrice = RESILIENT_ORACLE.getPrice(UNDERLYING_TOKEN);\n\n IERC20Metadata token = IERC20Metadata(CORRELATED_TOKEN);\n uint256 decimals = token.decimals();\n\n return (exchangeRate * underlyingUSDPrice) / (10 ** decimals);\n }\n\n /**\n * @notice Reverts if the call is not allowed by AccessControlManager\n * @param signature Method signature\n * @custom:error Unauthorized error is thrown if the call is not allowed\n */\n function _checkAccessAllowed(string memory signature) internal view {\n bool isAllowedToCall = ACCESS_CONTROL_MANAGER.isAllowedToCall(msg.sender, signature);\n\n if (!isAllowedToCall) {\n revert Unauthorized(msg.sender, address(this), signature);\n }\n }\n}\n" + }, + "contracts/oracles/ERC4626Oracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IERC4626 } from \"../interfaces/IERC4626.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title ERC4626Oracle\n * @author Venus\n * @notice This oracle fetches the price of ERC4626 tokens\n */\ncontract ERC4626Oracle is CorrelatedTokenOracle {\n uint256 public immutable ONE_CORRELATED_TOKEN;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ONE_CORRELATED_TOKEN = 10 ** IERC4626(correlatedToken).decimals();\n }\n\n /**\n * @notice Fetches the amount of underlying token for 1 correlated token\n * @return amount The amount of underlying token for correlated token\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IERC4626(CORRELATED_TOKEN).convertToAssets(ONE_CORRELATED_TOKEN);\n }\n}\n" + }, + "contracts/oracles/EtherfiAccountantOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IAccountant } from \"../interfaces/IAccountant.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title EtherfiAccountantOracle\n * @author Venus\n * @notice This oracle fetches the price of any Ether.fi asset that uses\n * Accountant contracts to derive the underlying price\n */\ncontract EtherfiAccountantOracle is CorrelatedTokenOracle {\n /// @notice Address of Accountant\n IAccountant public immutable ACCOUNTANT;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address accountant,\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(accountant);\n ACCOUNTANT = IAccountant(accountant);\n }\n\n /**\n * @notice Fetches the conversion rate from the ACCOUNTANT contract\n * @return amount Amount of WBTC\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ACCOUNTANT.getRateSafe();\n }\n}\n" + }, + "contracts/oracles/mocks/MockAccountant.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/IAccountant.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAccountant is IAccountant, Ownable {\n uint256 public rate;\n\n constructor() Ownable() {}\n\n function setRate(uint256 _rate) external onlyOwner {\n rate = _rate;\n }\n\n function getRateSafe() external view override returns (uint256) {\n return rate;\n }\n}\n" + }, + "contracts/oracles/mocks/MockBinanceFeedRegistry.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/FeedRegistryInterface.sol\";\n\ncontract MockBinanceFeedRegistry is FeedRegistryInterface {\n mapping(string => uint256) public assetPrices;\n\n function setAssetPrice(string memory base, uint256 price) external {\n assetPrices[base] = price;\n }\n\n function latestRoundDataByName(\n string memory base,\n string memory quote\n )\n external\n view\n override\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n quote;\n return (0, int256(assetPrices[base]), 0, block.timestamp - 10, 0);\n }\n\n function decimalsByName(string memory base, string memory quote) external view override returns (uint8) {\n return 8;\n }\n}\n" + }, + "contracts/oracles/mocks/MockBinanceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OwnableUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\nimport { OracleInterface } from \"../../interfaces/OracleInterface.sol\";\n\ncontract MockBinanceOracle is OwnableUpgradeable, OracleInterface {\n mapping(address => uint256) public assetPrices;\n\n constructor() {}\n\n function initialize() public initializer {}\n\n function setPrice(address asset, uint256 price) external {\n assetPrices[asset] = price;\n }\n\n function getPrice(address token) public view returns (uint256) {\n return assetPrices[token];\n }\n}\n" + }, + "contracts/oracles/mocks/MockChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OwnableUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol\";\nimport { OracleInterface } from \"../../interfaces/OracleInterface.sol\";\n\ncontract MockChainlinkOracle is OwnableUpgradeable, OracleInterface {\n mapping(address => uint256) public assetPrices;\n\n //set price in 6 decimal precision\n constructor() {}\n\n function initialize() public initializer {\n __Ownable_init();\n }\n\n function setPrice(address asset, uint256 price) external {\n assetPrices[asset] = price;\n }\n\n //https://compound.finance/docs/prices\n function getPrice(address token) public view returns (uint256) {\n return assetPrices[token];\n }\n}\n" + }, + "contracts/oracles/mocks/MockPendlePtOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/IPendlePtOracle.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockPendlePtOracle is IPendlePtOracle, Ownable {\n mapping(address => mapping(uint32 => uint256)) public ptToAssetRate;\n mapping(address => mapping(uint32 => uint256)) public ptToSyRate;\n\n constructor() Ownable() {}\n\n function setPtToAssetRate(address market, uint32 duration, uint256 rate) external onlyOwner {\n ptToAssetRate[market][duration] = rate;\n }\n\n function setPtToSyRate(address market, uint32 duration, uint256 rate) external onlyOwner {\n ptToSyRate[market][duration] = rate;\n }\n\n function getPtToAssetRate(address market, uint32 duration) external view returns (uint256) {\n return ptToAssetRate[market][duration];\n }\n\n function getPtToSyRate(address market, uint32 duration) external view returns (uint256) {\n return ptToSyRate[market][duration];\n }\n\n function getOracleState(\n address /* market */,\n uint32 /* duration */\n )\n external\n pure\n returns (bool increaseCardinalityRequired, uint16 cardinalityRequired, bool oldestObservationSatisfied)\n {\n return (false, 0, true);\n }\n}\n" + }, + "contracts/oracles/mocks/MockSFrxEthFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/ISfrxEthFraxOracle.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockSfrxEthFraxOracle is ISfrxEthFraxOracle, Ownable {\n bool public isBadData;\n uint256 public priceLow;\n uint256 public priceHigh;\n\n constructor() Ownable() {}\n\n function setPrices(bool _isBadData, uint256 _priceLow, uint256 _priceHigh) external onlyOwner {\n isBadData = _isBadData;\n priceLow = _priceLow;\n priceHigh = _priceHigh;\n }\n\n function getPrices() external view override returns (bool, uint256, uint256) {\n return (isBadData, priceLow, priceHigh);\n }\n}\n" + }, + "contracts/oracles/OneJumpOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\n/**\n * @title OneJumpOracle\n * @author Venus\n * @notice This oracle fetches the price of an asset in through an intermediate asset\n */\ncontract OneJumpOracle is CorrelatedTokenOracle {\n /// @notice Address of the intermediate oracle\n OracleInterface public immutable INTERMEDIATE_ORACLE;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n address intermediateOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(intermediateOracle);\n INTERMEDIATE_ORACLE = OracleInterface(intermediateOracle);\n }\n\n /**\n * @notice Fetches the amount of the underlying token for 1 correlated token, using the intermediate oracle\n * @return amount The amount of the underlying token for 1 correlated token scaled by the underlying token decimals\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n uint256 underlyingDecimals = IERC20Metadata(UNDERLYING_TOKEN).decimals();\n uint256 correlatedDecimals = IERC20Metadata(CORRELATED_TOKEN).decimals();\n\n uint256 underlyingAmount = INTERMEDIATE_ORACLE.getPrice(CORRELATED_TOKEN);\n\n return (underlyingAmount * (10 ** correlatedDecimals)) / (10 ** (36 - underlyingDecimals));\n }\n}\n" + }, + "contracts/oracles/PendleOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IPendlePtOracle } from \"../interfaces/IPendlePtOracle.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { IERC20Metadata } from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\n/**\n * @title PendleOracle\n * @author Venus\n * @notice This oracle fetches the price of a pendle token\n * @dev As a base price the oracle uses either the price of the Pendle\n * market's asset (in this case PT_TO_ASSET rate should be used) or\n * the price of the Pendle market's interest bearing token (e.g. wstETH\n * for stETH; in this case PT_TO_SY rate should be used). Technically,\n * interest bearing token is different from standardized yield (SY) token,\n * but since SY is a wrapper around an interest bearing token, we can safely\n * assume the prices of the two are equal. This is not always true for asset\n * price though: using PT_TO_ASSET rate assumes that the yield token can\n * be seamlessly redeemed for the underlying asset. In reality, this might\n * not always be the case. For more details, see\n * https://docs.pendle.finance/Developers/Contracts/StandardizedYield\n */\ncontract PendleOracle is CorrelatedTokenOracle {\n struct ConstructorParams {\n /// @notice Pendle market\n address market;\n /// @notice Pendle oracle\n address ptOracle;\n /// @notice Either PT_TO_ASSET or PT_TO_SY\n RateKind rateKind;\n /// @notice Pendle PT token\n address ptToken;\n /// @notice Underlying token, can be either the market's asset or the interest bearing token\n address underlyingToken;\n /// @notice Resilient oracle to get the underlying token price from\n address resilientOracle;\n /// @notice TWAP duration to call Pendle oracle with\n uint32 twapDuration;\n /// @notice Annual growth rate of the underlying token\n uint256 annualGrowthRate;\n /// @notice Snapshot interval for the oracle\n uint256 snapshotInterval;\n /// @notice Initial exchange rate of the underlying token\n uint256 initialSnapshotMaxExchangeRate;\n /// @notice Initial timestamp of the underlying token\n uint256 initialSnapshotTimestamp;\n /// @notice Access control manager\n address accessControlManager;\n /// @notice Gap to add when updating the snapshot\n uint256 snapshotGap;\n }\n\n /// @notice Which asset to use as a base for the returned PT\n /// price. Can be either a standardized yield token (SY), in\n /// this case PT/SY price is returned, or the Pendle\n /// market's asset directly.\n enum RateKind {\n PT_TO_ASSET,\n PT_TO_SY\n }\n\n /// @notice Address of the PT oracle\n IPendlePtOracle public immutable PT_ORACLE;\n\n /// @notice Whether to use PT/SY (standardized yield token) rate\n /// or PT/market asset rate\n RateKind public immutable RATE_KIND;\n\n /// @notice Address of the market\n address public immutable MARKET;\n\n /// @notice Twap duration for the oracle\n uint32 public immutable TWAP_DURATION;\n\n /// @notice Decimals of the underlying token\n /// @dev We make an assumption that the underlying decimals will\n /// not change throughout the lifetime of the Pendle market\n uint8 public immutable UNDERLYING_DECIMALS;\n\n /// @notice Thrown if the duration is invalid\n error InvalidDuration();\n\n /**\n * @notice Constructor for the implementation contract.\n * @custom:error InvalidDuration Thrown if the duration is invalid\n */\n constructor(\n ConstructorParams memory params\n )\n CorrelatedTokenOracle(\n params.ptToken,\n params.underlyingToken,\n params.resilientOracle,\n params.annualGrowthRate,\n params.snapshotInterval,\n params.initialSnapshotMaxExchangeRate,\n params.initialSnapshotTimestamp,\n params.accessControlManager,\n params.snapshotGap\n )\n {\n ensureNonzeroAddress(params.market);\n ensureNonzeroAddress(params.ptOracle);\n ensureNonzeroValue(params.twapDuration);\n\n MARKET = params.market;\n PT_ORACLE = IPendlePtOracle(params.ptOracle);\n RATE_KIND = params.rateKind;\n TWAP_DURATION = params.twapDuration;\n UNDERLYING_DECIMALS = IERC20Metadata(UNDERLYING_TOKEN).decimals();\n\n (bool increaseCardinalityRequired, , bool oldestObservationSatisfied) = PT_ORACLE.getOracleState(\n MARKET,\n TWAP_DURATION\n );\n if (increaseCardinalityRequired || !oldestObservationSatisfied) {\n revert InvalidDuration();\n }\n }\n\n /// @notice Fetches the amount of underlying token for 1 PT\n /// @return amount The amount of underlying token (either the market's asset\n /// or the yield token) for 1 PT, adjusted for decimals such that the result\n /// has the same precision as the underlying token\n function getUnderlyingAmount() public view override returns (uint256) {\n uint256 rate;\n if (RATE_KIND == RateKind.PT_TO_SY) {\n rate = PT_ORACLE.getPtToSyRate(MARKET, TWAP_DURATION);\n } else {\n rate = PT_ORACLE.getPtToAssetRate(MARKET, TWAP_DURATION);\n }\n return ((10 ** UNDERLYING_DECIMALS) * rate) / 1e18;\n }\n}\n" + }, + "contracts/oracles/SequencerChainlinkOracle.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.25;\n\nimport { ChainlinkOracle } from \"./ChainlinkOracle.sol\";\nimport { AggregatorV3Interface } from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\n\n/**\n @title Sequencer Chain Link Oracle\n @notice Oracle to fetch price using chainlink oracles on L2s with sequencer\n*/\ncontract SequencerChainlinkOracle is ChainlinkOracle {\n /// @notice L2 Sequencer feed\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n AggregatorV3Interface public immutable sequencer;\n\n /// @notice L2 Sequencer grace period\n uint256 public constant GRACE_PERIOD_TIME = 3600;\n\n /**\n @notice Contract constructor\n @param _sequencer L2 sequencer\n @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(AggregatorV3Interface _sequencer) ChainlinkOracle() {\n require(address(_sequencer) != address(0), \"zero address\");\n\n sequencer = _sequencer;\n }\n\n /// @inheritdoc ChainlinkOracle\n function getPrice(address asset) public view override returns (uint) {\n if (!isSequencerActive()) revert(\"L2 sequencer unavailable\");\n return super.getPrice(asset);\n }\n\n function isSequencerActive() internal view returns (bool) {\n // answer from oracle is a variable with a value of either 1 or 0\n // 0: The sequencer is up\n // 1: The sequencer is down\n // startedAt: This timestamp indicates when the sequencer changed status\n (, int256 answer, uint256 startedAt, , ) = sequencer.latestRoundData();\n if (block.timestamp - startedAt <= GRACE_PERIOD_TIME || answer == 1) return false;\n return true;\n }\n}\n" + }, + "contracts/oracles/SFraxOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISFrax } from \"../interfaces/ISFrax.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title SFraxOracle\n * @author Venus\n * @notice This oracle fetches the price of sFrax\n */\ncontract SFraxOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address sFrax,\n address frax,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n sFrax,\n frax,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of FRAX for 1 sFrax\n * @return amount The amount of FRAX for sFrax\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ISFrax(CORRELATED_TOKEN).convertToAssets(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/SFrxETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISfrxEthFraxOracle } from \"../interfaces/ISfrxEthFraxOracle.sol\";\nimport { ensureNonzeroAddress, ensureNonzeroValue } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title SFrxETHOracle\n * @author Venus\n * @notice This oracle fetches the price of sfrxETH\n */\ncontract SFrxETHOracle is AccessControlledV8, OracleInterface {\n /// @notice Address of SfrxEthFraxOracle\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ISfrxEthFraxOracle public immutable SFRXETH_FRAX_ORACLE;\n\n /// @notice Address of sfrxETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable SFRXETH;\n\n /// @notice Maximum allowed price difference\n uint256 public maxAllowedPriceDifference;\n\n /// @notice Emits when the maximum allowed price difference is updated\n event MaxAllowedPriceDifferenceUpdated(uint256 oldMaxAllowedPriceDifference, uint256 newMaxAllowedPriceDifference);\n\n /// @notice Thrown if the price data is invalid\n error BadPriceData();\n\n /// @notice Thrown if the price difference exceeds the allowed limit\n error PriceDifferenceExceeded();\n\n /// @notice Thrown if the token address is invalid\n error InvalidTokenAddress();\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n /// @custom:error ZeroAddressNotAllowed is thrown when `_sfrxEthFraxOracle` or `_sfrxETH` are the zero address\n constructor(address _sfrxEthFraxOracle, address _sfrxETH) {\n ensureNonzeroAddress(_sfrxEthFraxOracle);\n ensureNonzeroAddress(_sfrxETH);\n\n SFRXETH_FRAX_ORACLE = ISfrxEthFraxOracle(_sfrxEthFraxOracle);\n SFRXETH = _sfrxETH;\n\n _disableInitializers();\n }\n\n /**\n * @notice Sets the contracts required to fetch prices\n * @param _acm Address of the access control manager contract\n * @param _maxAllowedPriceDifference Maximum allowed price difference\n * @custom:error ZeroValueNotAllowed is thrown if `_maxAllowedPriceDifference` is zero\n */\n function initialize(address _acm, uint256 _maxAllowedPriceDifference) external initializer {\n ensureNonzeroValue(_maxAllowedPriceDifference);\n\n __AccessControlled_init(_acm);\n maxAllowedPriceDifference = _maxAllowedPriceDifference;\n }\n\n /**\n * @notice Sets the maximum allowed price difference\n * @param _maxAllowedPriceDifference Maximum allowed price difference\n * @custom:error ZeroValueNotAllowed is thrown if `_maxAllowedPriceDifference` is zero\n */\n function setMaxAllowedPriceDifference(uint256 _maxAllowedPriceDifference) external {\n _checkAccessAllowed(\"setMaxAllowedPriceDifference(uint256)\");\n ensureNonzeroValue(_maxAllowedPriceDifference);\n\n emit MaxAllowedPriceDifferenceUpdated(maxAllowedPriceDifference, _maxAllowedPriceDifference);\n maxAllowedPriceDifference = _maxAllowedPriceDifference;\n }\n\n /**\n * @notice Fetches the USD price of sfrxETH\n * @param asset Address of the sfrxETH token\n * @return price The price scaled by 1e18\n * @custom:error InvalidTokenAddress is thrown when the `asset` is not the sfrxETH token (`SFRXETH`)\n * @custom:error BadPriceData is thrown if the `SFRXETH_FRAX_ORACLE` oracle informs it has bad data\n * @custom:error ZeroValueNotAllowed is thrown if the prices (low or high, in USD) are zero\n * @custom:error PriceDifferenceExceeded is thrown if priceHigh/priceLow is greater than `maxAllowedPriceDifference`\n */\n function getPrice(address asset) external view returns (uint256) {\n if (asset != SFRXETH) revert InvalidTokenAddress();\n\n (bool isBadData, uint256 priceLow, uint256 priceHigh) = SFRXETH_FRAX_ORACLE.getPrices();\n\n if (isBadData) revert BadPriceData();\n\n // calculate price in USD\n uint256 priceHighInUSD = (EXP_SCALE ** 2) / priceLow;\n uint256 priceLowInUSD = (EXP_SCALE ** 2) / priceHigh;\n\n ensureNonzeroValue(priceHighInUSD);\n ensureNonzeroValue(priceLowInUSD);\n\n // validate price difference\n uint256 difference = (priceHighInUSD * EXP_SCALE) / priceLowInUSD;\n if (difference > maxAllowedPriceDifference) revert PriceDifferenceExceeded();\n\n // calculate and return average price\n return (priceHighInUSD + priceLowInUSD) / 2;\n }\n}\n" + }, + "contracts/oracles/SlisBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ISynclubStakeManager } from \"../interfaces/ISynclubStakeManager.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title SlisBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of slisBNB asset\n */\ncontract SlisBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakeManager\n ISynclubStakeManager public immutable STAKE_MANAGER;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakeManager,\n address slisBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n slisBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakeManager);\n STAKE_MANAGER = ISynclubStakeManager(stakeManager);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 slisBNB\n * @return amount The amount of BNB for slisBNB\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STAKE_MANAGER.convertSnBnbToBnb(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/StableUsdtPriceFeed.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { ResilientOracleInterface } from \"../interfaces/OracleInterface.sol\";\n\n/**\n * @title StableUsdtPriceFeed\n * @dev This contract is used to get the price of USDT from a Resilient Oracle\n * and bounds the price to a certain range.\n */\ncontract StableUsdtPriceFeed {\n ResilientOracleInterface public resilientOracle;\n\n address public constant USDT_TOKEN_ADDR = 0x55d398326f99059fF775485246999027B3197955;\n uint256 public constant UPPER_BOUND = 1020000000000000000; // 1.02 USD\n uint256 public constant LOWER_BOUND = 980000000000000000; // 0.98 USD\n\n constructor(address _resilientOracle) {\n require(_resilientOracle != address(0), \"Zero address provided\");\n resilientOracle = ResilientOracleInterface(_resilientOracle);\n }\n\n function latestAnswer() external view returns (int256 answer) {\n // get price\n uint256 price = getPrice();\n // cast price to int256\n answer = int256(price);\n }\n\n function latestRoundData()\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n // get price\n uint256 _answer = getPrice();\n // mock timestamp to latest block timestamp\n uint256 timestamp = block.timestamp;\n // mock roundId to timestamp\n roundId = uint80(timestamp);\n return (roundId, int256(_answer), timestamp, timestamp, roundId);\n }\n\n function decimals() external pure returns (uint8) {\n return 18;\n }\n\n function description() external pure returns (string memory) {\n return \"Stabilized USDT Price Feed\";\n }\n\n function version() external pure returns (uint256) {\n return 1;\n }\n\n /**\n * @dev Get the price from the Resilient Oracle, and bound it to the range\n * @return price The price of USDT in 18 decimals\n */\n function getPrice() private view returns (uint256 price) {\n // get USDT price (18 decimals)\n price = resilientOracle.getPrice(USDT_TOKEN_ADDR);\n price = price < LOWER_BOUND ? LOWER_BOUND : (price > UPPER_BOUND ? UPPER_BOUND : price);\n }\n}\n" + }, + "contracts/oracles/StkBNBOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IPStakePool } from \"../interfaces/IPStakePool.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title StkBNBOracle\n * @author Venus\n * @notice This oracle fetches the price of stkBNB asset\n */\ncontract StkBNBOracle is CorrelatedTokenOracle {\n /// @notice This is used as token address of BNB on BSC\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Address of StakePool\n IPStakePool public immutable STAKE_POOL;\n\n /// @notice Thrown if the pool token supply is zero\n error PoolTokenSupplyIsZero();\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address stakePool,\n address stkBNB,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n stkBNB,\n NATIVE_TOKEN_ADDR,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stakePool);\n STAKE_POOL = IPStakePool(stakePool);\n }\n\n /**\n * @notice Fetches the amount of BNB for 1 stkBNB\n * @return price The amount of BNB for stkBNB\n * @custom:error PoolTokenSupplyIsZero error is thrown if the pool token supply is zero\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n IPStakePool.Data memory exchangeRateData = STAKE_POOL.exchangeRate();\n\n if (exchangeRateData.poolTokenSupply == 0) {\n revert PoolTokenSupplyIsZero();\n }\n\n return (exchangeRateData.totalWei * EXP_SCALE) / exchangeRateData.poolTokenSupply;\n }\n}\n" + }, + "contracts/oracles/WBETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IWBETH } from \"../interfaces/IWBETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title WBETHOracle\n * @author Venus\n * @notice This oracle fetches the price of wBETH asset\n */\ncontract WBETHOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address wbeth,\n address eth,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n wbeth,\n eth,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Fetches the amount of ETH for 1 wBETH\n * @return amount The amount of ETH for wBETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IWBETH(CORRELATED_TOKEN).exchangeRate();\n }\n}\n" + }, + "contracts/oracles/WeETHAccountantOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IAccountant } from \"../interfaces/IAccountant.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WeETHAccountantOracle\n * @author Venus\n * @notice This oracle fetches the price of Ether.fi tokens based on an `Accountant` contract (i.e. weETHs and weETHk)\n */\ncontract WeETHAccountantOracle is CorrelatedTokenOracle {\n /// @notice Address of Accountant\n IAccountant public immutable ACCOUNTANT;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address accountant,\n address weethLRT,\n address weth,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n weethLRT,\n weth,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(accountant);\n ACCOUNTANT = IAccountant(accountant);\n }\n\n /**\n * @notice Gets the WETH for 1 weETH LRT\n * @return amount Amount of WETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return ACCOUNTANT.getRateSafe();\n }\n}\n" + }, + "contracts/oracles/WeETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { IEtherFiLiquidityPool } from \"../interfaces/IEtherFiLiquidityPool.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WeETHOracle\n * @author Venus\n * @notice This oracle fetches the price of weETH\n */\ncontract WeETHOracle is CorrelatedTokenOracle {\n /// @notice Address of Liqiudity pool\n IEtherFiLiquidityPool public immutable LIQUIDITY_POOL;\n\n /// @notice Constructor for the implementation contract.\n constructor(\n address liquidityPool,\n address weETH,\n address eETH,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n weETH,\n eETH,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(liquidityPool);\n LIQUIDITY_POOL = IEtherFiLiquidityPool(liquidityPool);\n }\n\n /**\n * @notice Gets the eETH for 1 weETH\n * @return amount Amount of eETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return LIQUIDITY_POOL.amountForShare(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/WstETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { OracleInterface } from \"../interfaces/OracleInterface.sol\";\nimport { IStETH } from \"../interfaces/IStETH.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\n\n/**\n * @title WstETHOracle\n * @author Venus\n * @notice Depending on the equivalence flag price is either based on assumption that 1 stETH = 1 ETH\n * or the price of stETH/USD (secondary market price) is obtained from the oracle.\n */\ncontract WstETHOracle is OracleInterface {\n /// @notice A flag assuming 1:1 price equivalence between stETH/ETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n bool public immutable ASSUME_STETH_ETH_EQUIVALENCE;\n\n /// @notice Address of stETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n IStETH public immutable STETH;\n\n /// @notice Address of wstETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable WSTETH_ADDRESS;\n\n /// @notice Address of WETH\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable WETH_ADDRESS;\n\n /// @notice Address of Resilient Oracle\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n OracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Constructor for the implementation contract.\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(\n address wstETHAddress,\n address wETHAddress,\n address stETHAddress,\n address resilientOracleAddress,\n bool assumeEquivalence\n ) {\n ensureNonzeroAddress(wstETHAddress);\n ensureNonzeroAddress(wETHAddress);\n ensureNonzeroAddress(stETHAddress);\n ensureNonzeroAddress(resilientOracleAddress);\n WSTETH_ADDRESS = wstETHAddress;\n WETH_ADDRESS = wETHAddress;\n STETH = IStETH(stETHAddress);\n RESILIENT_ORACLE = OracleInterface(resilientOracleAddress);\n ASSUME_STETH_ETH_EQUIVALENCE = assumeEquivalence;\n }\n\n /**\n * @notice Gets the USD price of wstETH asset\n * @dev Depending on the equivalence flag price is either based on assumption that 1 stETH = 1 ETH\n * or the price of stETH/USD (secondary market price) is obtained from the oracle\n * @param asset Address of wstETH\n * @return wstETH Price in USD scaled by 1e18\n */\n function getPrice(address asset) public view returns (uint256) {\n if (asset != WSTETH_ADDRESS) revert(\"wrong wstETH address\");\n\n // get stETH amount for 1 wstETH scaled by 1e18\n uint256 stETHAmount = STETH.getPooledEthByShares(1 ether);\n\n // price is scaled 1e18 (oracle returns 36 - asset decimal scale)\n uint256 stETHUSDPrice = RESILIENT_ORACLE.getPrice(ASSUME_STETH_ETH_EQUIVALENCE ? WETH_ADDRESS : address(STETH));\n\n // stETHAmount (for 1 wstETH) * stETHUSDPrice / 1e18\n return (stETHAmount * stETHUSDPrice) / EXP_SCALE;\n }\n}\n" + }, + "contracts/oracles/WstETHOracleV2.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IStETH } from \"../interfaces/IStETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\nimport { EXP_SCALE } from \"@venusprotocol/solidity-utilities/contracts/constants.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\n\n/**\n * @title WstETHOracleV2\n * @author Venus\n * @notice This oracle fetches the price of wstETH\n */\ncontract WstETHOracleV2 is CorrelatedTokenOracle {\n /// @notice Address of stETH\n IStETH public immutable STETH;\n\n /// @notice Constructor for the implementation contract.\n /// @dev The underlyingToken must be correlated so that 1 underlyingToken is equal to 1 stETH, because\n /// getUnderlyingAmount() implicitly assumes that\n constructor(\n address stETH,\n address wstETH,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n wstETH,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {\n ensureNonzeroAddress(stETH);\n STETH = IStETH(stETH);\n }\n\n /**\n * @notice Gets the amount of underlyingToken for 1 wstETH, assuming that 1 underlyingToken is equivalent to 1 stETH\n * @return amount Amount of underlyingToken\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return STETH.getPooledEthByShares(EXP_SCALE);\n }\n}\n" + }, + "contracts/oracles/ZkETHOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IZkETH } from \"../interfaces/IZkETH.sol\";\nimport { CorrelatedTokenOracle } from \"./common/CorrelatedTokenOracle.sol\";\n\n/**\n * @title ZkETHOracle\n * @author Venus\n * @notice This oracle fetches the price of zkETH\n */\ncontract ZkETHOracle is CorrelatedTokenOracle {\n /// @notice Constructor for the implementation contract.\n constructor(\n address zkETH,\n address rzkETH,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 _snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 _snapshotGap\n )\n CorrelatedTokenOracle(\n zkETH,\n rzkETH,\n resilientOracle,\n annualGrowthRate,\n _snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n _snapshotGap\n )\n {}\n\n /**\n * @notice Gets the amount of rzkETH for 1 zkETH\n * @return amount Amount of rzkETH\n */\n function getUnderlyingAmount() public view override returns (uint256) {\n return IZkETH(CORRELATED_TOKEN).LSTPerToken();\n }\n}\n" + }, + "contracts/ReferenceOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2025 Venus\npragma solidity 0.8.25;\n\nimport { Ownable2StepUpgradeable } from \"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol\";\nimport { ensureNonzeroAddress } from \"@venusprotocol/solidity-utilities/contracts/validators.sol\";\nimport { ResilientOracleInterface, OracleInterface } from \"./interfaces/OracleInterface.sol\";\n\n/**\n * @title ReferenceOracle\n * @author Venus\n * @notice Reference oracle is the oracle that is not used for production but required for\n * price monitoring. This oracle contains some extra configurations for assets required to\n * compute reference prices of their derivative assets (OneJump, ERC4626, Pendle, etc.)\n */\ncontract ReferenceOracle is Ownable2StepUpgradeable, OracleInterface {\n struct ExternalPrice {\n /// @notice asset address\n address asset;\n /// @notice price of the asset from an external source\n uint256 price;\n }\n\n /// @notice Slot to temporarily store price information from external sources\n /// like CMC/Coingecko, useful to compute prices of derivative assets based on\n /// prices of the base assets with no on chain price information\n bytes32 public constant PRICES_SLOT = keccak256(abi.encode(\"venus-protocol/oracle/ReferenceOracle/prices\"));\n\n /// @notice Resilient oracle address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n ResilientOracleInterface public immutable RESILIENT_ORACLE;\n\n /// @notice Oracle configuration for assets\n mapping(address => OracleInterface) public oracles;\n\n /// @notice Event emitted when an oracle is set\n event OracleConfigured(address indexed asset, address indexed oracle);\n\n /**\n * @notice Constructor for the implementation contract. Sets immutable variables.\n * @param resilientOracle Resilient oracle address\n * @custom:error ZeroAddressNotAllowed is thrown if resilient oracle address is null\n * @custom:oz-upgrades-unsafe-allow constructor\n */\n constructor(ResilientOracleInterface resilientOracle) {\n ensureNonzeroAddress(address(resilientOracle));\n RESILIENT_ORACLE = resilientOracle;\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin\n */\n function initialize() external initializer {\n __Ownable2Step_init();\n }\n\n /**\n * @notice Sets an oracle to use for a specific asset\n * @dev The production resilientOracle will be used if zero address is passed\n * @param asset Asset address\n * @param oracle Oracle address\n * @custom:access Only owner\n * @custom:error ZeroAddressNotAllowed is thrown if asset address is null\n * @custom:event Emits OracleConfigured event\n */\n function setOracle(address asset, OracleInterface oracle) external onlyOwner {\n ensureNonzeroAddress(asset);\n oracles[asset] = OracleInterface(oracle);\n emit OracleConfigured(asset, address(oracle));\n }\n\n /**\n * @notice Gets price of the asset assuming other assets have the defined price\n * @param asset asset address\n * @param externalPrices an array of prices for other assets\n * @return USD price in scaled decimal places\n */\n function getPriceAssuming(address asset, ExternalPrice[] memory externalPrices) external returns (uint256) {\n uint256 externalPricesCount = externalPrices.length;\n for (uint256 i = 0; i < externalPricesCount; ++i) {\n _storeExternalPrice(externalPrices[i].asset, externalPrices[i].price);\n }\n return _getPrice(asset);\n }\n\n /**\n * @notice Gets price of the asset\n * @param asset asset address\n * @return USD price in scaled decimal places\n */\n function getPrice(address asset) external view override returns (uint256) {\n return _getPrice(asset);\n }\n\n function _storeExternalPrice(address asset, uint256 price) internal {\n bytes32 slot = keccak256(abi.encode(PRICES_SLOT, asset));\n // solhint-disable-next-line no-inline-assembly\n assembly (\"memory-safe\") {\n tstore(slot, price)\n }\n }\n\n function _getPrice(address asset) internal view returns (uint256) {\n uint256 externalPrice = _loadExternalPrice(asset);\n if (externalPrice != 0) {\n return externalPrice;\n }\n OracleInterface oracle = oracles[asset];\n if (oracle != OracleInterface(address(0))) {\n return oracle.getPrice(asset);\n }\n return RESILIENT_ORACLE.getPrice(asset);\n }\n\n function _loadExternalPrice(address asset) internal view returns (uint256 value) {\n bytes32 slot = keccak256(abi.encode(PRICES_SLOT, asset));\n // solhint-disable-next-line no-inline-assembly\n assembly (\"memory-safe\") {\n value := tload(slot)\n }\n }\n}\n" + }, + "contracts/ResilientOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n// SPDX-FileCopyrightText: 2022 Venus\npragma solidity 0.8.25;\n\nimport { PausableUpgradeable } from \"@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol\";\nimport { VBep20Interface } from \"./interfaces/VBep20Interface.sol\";\nimport { OracleInterface, ResilientOracleInterface, BoundValidatorInterface } from \"./interfaces/OracleInterface.sol\";\nimport { AccessControlledV8 } from \"@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol\";\nimport { ICappedOracle } from \"./interfaces/ICappedOracle.sol\";\nimport { Transient } from \"./lib/Transient.sol\";\n\n/**\n * @title ResilientOracle\n * @author Venus\n * @notice The Resilient Oracle is the main contract that the protocol uses to fetch prices of assets.\n *\n * DeFi protocols are vulnerable to price oracle failures including oracle manipulation and incorrectly\n * reported prices. If only one oracle is used, this creates a single point of failure and opens a vector\n * for attacking the protocol.\n *\n * The Resilient Oracle uses multiple sources and fallback mechanisms to provide accurate prices and protect\n * the protocol from oracle attacks.\n *\n * For every market (vToken) we configure the main, pivot and fallback oracles. The oracles are configured per\n * vToken's underlying asset address. The main oracle oracle is the most trustworthy price source, the pivot\n * oracle is used as a loose sanity checker and the fallback oracle is used as a backup price source.\n *\n * To validate prices returned from two oracles, we use an upper and lower bound ratio that is set for every\n * market. The upper bound ratio represents the deviation between reported price (the price that’s being\n * validated) and the anchor price (the price we are validating against) above which the reported price will\n * be invalidated. The lower bound ratio presents the deviation between reported price and anchor price below\n * which the reported price will be invalidated. So for oracle price to be considered valid the below statement\n * should be true:\n\n```\nanchorRatio = anchorPrice/reporterPrice\nisValid = anchorRatio <= upperBoundAnchorRatio && anchorRatio >= lowerBoundAnchorRatio\n```\n\n * In most cases, Chainlink is used as the main oracle, other oracles are used as the pivot oracle depending\n * on which supports the given market and Binance oracle is used as the fallback oracle.\n *\n * For a fetched price to be valid it must be positive and not stagnant. If the price is invalid then we consider the\n * oracle to be stagnant and treat it like it's disabled.\n */\ncontract ResilientOracle is PausableUpgradeable, AccessControlledV8, ResilientOracleInterface {\n /**\n * @dev Oracle roles:\n * **main**: The most trustworthy price source\n * **pivot**: Price oracle used as a loose sanity checker\n * **fallback**: The backup source when main oracle price is invalidated\n */\n enum OracleRole {\n MAIN,\n PIVOT,\n FALLBACK\n }\n\n struct TokenConfig {\n /// @notice asset address\n address asset;\n /// @notice `oracles` stores the oracles based on their role in the following order:\n /// [main, pivot, fallback],\n /// It can be indexed with the corresponding enum OracleRole value\n address[3] oracles;\n /// @notice `enableFlagsForOracles` stores the enabled state\n /// for each oracle in the same order as `oracles`\n bool[3] enableFlagsForOracles;\n /// @notice `cachingEnabled` is a flag that indicates whether the asset price should be cached\n bool cachingEnabled;\n }\n\n uint256 public constant INVALID_PRICE = 0;\n\n /// @notice Native market address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable nativeMarket;\n\n /// @notice VAI address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address public immutable vai;\n\n /// @notice Set this as asset address for Native token on each chain.This is the underlying for vBNB (on bsc)\n /// and can serve as any underlying asset of a market that supports native tokens\n address public constant NATIVE_TOKEN_ADDR = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB;\n\n /// @notice Slot to cache the asset's price, used for transient storage\n /// custom:storage-location erc7201:venus-protocol/oracle/ResilientOracle/cache\n /// keccak256(abi.encode(uint256(keccak256(\"venus-protocol/oracle/ResilientOracle/cache\")) - 1))\n /// & ~bytes32(uint256(0xff))\n bytes32 public constant CACHE_SLOT = 0x4e99ec55972332f5e0ef9c6623192c0401b609161bffae64d9ccdd7ad6cc7800;\n\n /// @notice Bound validator contract address\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n BoundValidatorInterface public immutable boundValidator;\n\n mapping(address => TokenConfig) private tokenConfigs;\n\n event TokenConfigAdded(\n address indexed asset,\n address indexed mainOracle,\n address indexed pivotOracle,\n address fallbackOracle\n );\n\n /// Event emitted when an oracle is set\n event OracleSet(address indexed asset, address indexed oracle, uint256 indexed role);\n\n /// Event emitted when an oracle is enabled or disabled\n event OracleEnabled(address indexed asset, uint256 indexed role, bool indexed enable);\n\n /// Event emitted when an asset cachingEnabled flag is set\n event CachedEnabled(address indexed asset, bool indexed enabled);\n\n /**\n * @notice Checks whether an address is null or not\n */\n modifier notNullAddress(address someone) {\n if (someone == address(0)) revert(\"can't be zero address\");\n _;\n }\n\n /**\n * @notice Checks whether token config exists by checking whether asset is null address\n * @dev address can't be null, so it's suitable to be used to check the validity of the config\n * @param asset asset address\n */\n modifier checkTokenConfigExistence(address asset) {\n if (tokenConfigs[asset].asset == address(0)) revert(\"token config must exist\");\n _;\n }\n\n /// @notice Constructor for the implementation contract. Sets immutable variables.\n /// @dev nativeMarketAddress can be address(0) if on the chain we do not support native market\n /// (e.g vETH on ethereum would not be supported, only vWETH)\n /// @param nativeMarketAddress The address of a native market (for bsc it would be vBNB address)\n /// @param vaiAddress The address of the VAI token (if there is VAI on the deployed chain).\n /// Set to address(0) of VAI is not existent.\n /// @param _boundValidator Address of the bound validator contract\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(\n address nativeMarketAddress,\n address vaiAddress,\n BoundValidatorInterface _boundValidator\n ) notNullAddress(address(_boundValidator)) {\n nativeMarket = nativeMarketAddress;\n vai = vaiAddress;\n boundValidator = _boundValidator;\n\n _disableInitializers();\n }\n\n /**\n * @notice Initializes the contract admin and sets the BoundValidator contract address\n * @param accessControlManager_ Address of the access control manager contract\n */\n function initialize(address accessControlManager_) external initializer {\n __AccessControlled_init(accessControlManager_);\n __Pausable_init();\n }\n\n /**\n * @notice Pauses oracle\n * @custom:access Only Governance\n */\n function pause() external {\n _checkAccessAllowed(\"pause()\");\n _pause();\n }\n\n /**\n * @notice Unpauses oracle\n * @custom:access Only Governance\n */\n function unpause() external {\n _checkAccessAllowed(\"unpause()\");\n _unpause();\n }\n\n /**\n * @notice Batch sets token configs\n * @param tokenConfigs_ Token config array\n * @custom:access Only Governance\n * @custom:error Throws a length error if the length of the token configs array is 0\n */\n function setTokenConfigs(TokenConfig[] memory tokenConfigs_) external {\n if (tokenConfigs_.length == 0) revert(\"length can't be 0\");\n uint256 numTokenConfigs = tokenConfigs_.length;\n for (uint256 i; i < numTokenConfigs; ++i) {\n setTokenConfig(tokenConfigs_[i]);\n }\n }\n\n /**\n * @notice Sets oracle for a given asset and role.\n * @dev Supplied asset **must** exist and main oracle may not be null\n * @param asset Asset address\n * @param oracle Oracle address\n * @param role Oracle role\n * @custom:access Only Governance\n * @custom:error Null address error if main-role oracle address is null\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error TokenConfigExistance error is thrown if token config is not set\n * @custom:event Emits OracleSet event with asset address, oracle address and role of the oracle for the asset\n */\n function setOracle(\n address asset,\n address oracle,\n OracleRole role\n ) external notNullAddress(asset) checkTokenConfigExistence(asset) {\n _checkAccessAllowed(\"setOracle(address,address,uint8)\");\n if (oracle == address(0) && role == OracleRole.MAIN) revert(\"can't set zero address to main oracle\");\n tokenConfigs[asset].oracles[uint256(role)] = oracle;\n emit OracleSet(asset, oracle, uint256(role));\n }\n\n /**\n * @notice Enables/ disables oracle for the input asset. Token config for the input asset **must** exist\n * @dev Configuration for the asset **must** already exist and the asset cannot be 0 address\n * @param asset Asset address\n * @param role Oracle role\n * @param enable Enabled boolean of the oracle\n * @custom:access Only Governance\n * @custom:error NotNullAddress error is thrown if asset address is null\n * @custom:error TokenConfigExistance error is thrown if token config is not set\n * @custom:event Emits OracleEnabled event with asset address, role of the oracle and enabled flag\n */\n function enableOracle(\n address asset,\n OracleRole role,\n bool enable\n ) external notNullAddress(asset) checkTokenConfigExistence(asset) {\n _checkAccessAllowed(\"enableOracle(address,uint8,bool)\");\n tokenConfigs[asset].enableFlagsForOracles[uint256(role)] = enable;\n emit OracleEnabled(asset, uint256(role), enable);\n }\n\n /**\n * @notice Updates the capped main oracle snapshot.\n * @dev This function should always be called before calling getUnderlyingPrice\n * @param vToken vToken address\n */\n function updatePrice(address vToken) external override {\n address asset = _getUnderlyingAsset(vToken);\n _updateAssetPrice(asset);\n }\n\n /**\n * @notice Updates the capped main oracle snapshot.\n * @dev This function should always be called before calling getPrice\n * @param asset asset address\n */\n function updateAssetPrice(address asset) external {\n _updateAssetPrice(asset);\n }\n\n /**\n * @dev Gets token config by asset address\n * @param asset asset address\n * @return tokenConfig Config for the asset\n */\n function getTokenConfig(address asset) external view returns (TokenConfig memory) {\n return tokenConfigs[asset];\n }\n\n /**\n * @notice Gets price of the underlying asset for a given vToken. Validation flow:\n * - Check if the oracle is paused globally\n * - Validate price from main oracle against pivot oracle\n * - Validate price from fallback oracle against pivot oracle if the first validation failed\n * - Validate price from main oracle against fallback oracle if the second validation failed\n * In the case that the pivot oracle is not available but main price is available and validation is successful,\n * main oracle price is returned.\n * @param vToken vToken address\n * @return price USD price in scaled decimal places.\n * @custom:error Paused error is thrown when resilent oracle is paused\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function getUnderlyingPrice(address vToken) external view override returns (uint256) {\n if (paused()) revert(\"resilient oracle is paused\");\n\n address asset = _getUnderlyingAsset(vToken);\n return _getPrice(asset);\n }\n\n /**\n * @notice Gets price of the asset\n * @param asset asset address\n * @return price USD price in scaled decimal places.\n * @custom:error Paused error is thrown when resilent oracle is paused\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function getPrice(address asset) external view override returns (uint256) {\n if (paused()) revert(\"resilient oracle is paused\");\n return _getPrice(asset);\n }\n\n /**\n * @notice Sets/resets single token configs.\n * @dev main oracle **must not** be a null address\n * @param tokenConfig Token config struct\n * @custom:access Only Governance\n * @custom:error NotNullAddress is thrown if asset address is null\n * @custom:error NotNullAddress is thrown if main-role oracle address for asset is null\n * @custom:event Emits TokenConfigAdded event when the asset config is set successfully by the authorized account\n * @custom:event Emits CachedEnabled event when the asset cachingEnabled flag is set successfully\n */\n function setTokenConfig(\n TokenConfig memory tokenConfig\n ) public notNullAddress(tokenConfig.asset) notNullAddress(tokenConfig.oracles[uint256(OracleRole.MAIN)]) {\n _checkAccessAllowed(\"setTokenConfig(TokenConfig)\");\n\n tokenConfigs[tokenConfig.asset] = tokenConfig;\n emit TokenConfigAdded(\n tokenConfig.asset,\n tokenConfig.oracles[uint256(OracleRole.MAIN)],\n tokenConfig.oracles[uint256(OracleRole.PIVOT)],\n tokenConfig.oracles[uint256(OracleRole.FALLBACK)]\n );\n emit CachedEnabled(tokenConfig.asset, tokenConfig.cachingEnabled);\n }\n\n /**\n * @notice Gets oracle and enabled status by asset address\n * @param asset asset address\n * @param role Oracle role\n * @return oracle Oracle address based on role\n * @return enabled Enabled flag of the oracle based on token config\n */\n function getOracle(address asset, OracleRole role) public view returns (address oracle, bool enabled) {\n oracle = tokenConfigs[asset].oracles[uint256(role)];\n enabled = tokenConfigs[asset].enableFlagsForOracles[uint256(role)];\n }\n\n /**\n * @notice Updates the capped oracle snapshot.\n * @dev Cache the asset price and return if already cached\n * @param asset asset address\n */\n function _updateAssetPrice(address asset) internal {\n if (Transient.readCachedPrice(CACHE_SLOT, asset) != 0) {\n return;\n }\n\n (address mainOracle, bool mainOracleEnabled) = getOracle(asset, OracleRole.MAIN);\n if (mainOracle != address(0) && mainOracleEnabled) {\n // if main oracle is not CorrelatedTokenOracle it will revert so we need to catch the revert\n try ICappedOracle(mainOracle).updateSnapshot() {} catch {}\n }\n\n if (_isCacheEnabled(asset)) {\n uint256 price = _getPrice(asset);\n Transient.cachePrice(CACHE_SLOT, asset, price);\n }\n }\n\n /**\n * @notice Gets price for the provided asset\n * @param asset asset address\n * @return price USD price in scaled decimal places.\n * @custom:error Invalid resilient oracle price error is thrown if fetched prices from oracle is invalid\n */\n function _getPrice(address asset) internal view returns (uint256) {\n uint256 pivotPrice = INVALID_PRICE;\n uint256 price;\n\n price = Transient.readCachedPrice(CACHE_SLOT, asset);\n if (price != 0) {\n return price;\n }\n\n // Get pivot oracle price, Invalid price if not available or error\n (address pivotOracle, bool pivotOracleEnabled) = getOracle(asset, OracleRole.PIVOT);\n if (pivotOracleEnabled && pivotOracle != address(0)) {\n try OracleInterface(pivotOracle).getPrice(asset) returns (uint256 pricePivot) {\n pivotPrice = pricePivot;\n } catch {}\n }\n\n // Compare main price and pivot price, return main price and if validation was successful\n // note: In case pivot oracle is not available but main price is available and\n // validation is successful, the main oracle price is returned.\n (uint256 mainPrice, bool validatedPivotMain) = _getMainOraclePrice(\n asset,\n pivotPrice,\n pivotOracleEnabled && pivotOracle != address(0)\n );\n if (mainPrice != INVALID_PRICE && validatedPivotMain) return mainPrice;\n\n // Compare fallback and pivot if main oracle comparision fails with pivot\n // Return fallback price when fallback price is validated successfully with pivot oracle\n (uint256 fallbackPrice, bool validatedPivotFallback) = _getFallbackOraclePrice(asset, pivotPrice);\n if (fallbackPrice != INVALID_PRICE && validatedPivotFallback) return fallbackPrice;\n\n // Lastly compare main price and fallback price\n if (\n mainPrice != INVALID_PRICE &&\n fallbackPrice != INVALID_PRICE &&\n boundValidator.validatePriceWithAnchorPrice(asset, mainPrice, fallbackPrice)\n ) {\n return mainPrice;\n }\n\n revert(\"invalid resilient oracle price\");\n }\n\n /**\n * @notice Gets a price for the provided asset\n * @dev This function won't revert when price is 0, because the fallback oracle may still be\n * able to fetch a correct price\n * @param asset asset address\n * @param pivotPrice Pivot oracle price\n * @param pivotEnabled If pivot oracle is not empty and enabled\n * @return price USD price in scaled decimals\n * e.g. asset decimals is 8 then price is returned as 10**18 * 10**(18-8) = 10**28 decimals\n * @return pivotValidated Boolean representing if the validation of main oracle price\n * and pivot oracle price were successful\n * @custom:error Invalid price error is thrown if main oracle fails to fetch price of the asset\n * @custom:error Invalid price error is thrown if main oracle is not enabled or main oracle\n * address is null\n */\n function _getMainOraclePrice(\n address asset,\n uint256 pivotPrice,\n bool pivotEnabled\n ) internal view returns (uint256, bool) {\n (address mainOracle, bool mainOracleEnabled) = getOracle(asset, OracleRole.MAIN);\n if (mainOracleEnabled && mainOracle != address(0)) {\n try OracleInterface(mainOracle).getPrice(asset) returns (uint256 mainOraclePrice) {\n if (!pivotEnabled) {\n return (mainOraclePrice, true);\n }\n if (pivotPrice == INVALID_PRICE) {\n return (mainOraclePrice, false);\n }\n return (\n mainOraclePrice,\n boundValidator.validatePriceWithAnchorPrice(asset, mainOraclePrice, pivotPrice)\n );\n } catch {\n return (INVALID_PRICE, false);\n }\n }\n\n return (INVALID_PRICE, false);\n }\n\n /**\n * @dev This function won't revert when the price is 0 because getPrice checks if price is > 0\n * @param asset asset address\n * @return price USD price in 18 decimals\n * @return pivotValidated Boolean representing if the validation of fallback oracle price\n * and pivot oracle price were successfully\n * @custom:error Invalid price error is thrown if fallback oracle fails to fetch price of the asset\n * @custom:error Invalid price error is thrown if fallback oracle is not enabled or fallback oracle\n * address is null\n */\n function _getFallbackOraclePrice(address asset, uint256 pivotPrice) private view returns (uint256, bool) {\n (address fallbackOracle, bool fallbackEnabled) = getOracle(asset, OracleRole.FALLBACK);\n if (fallbackEnabled && fallbackOracle != address(0)) {\n try OracleInterface(fallbackOracle).getPrice(asset) returns (uint256 fallbackOraclePrice) {\n if (pivotPrice == INVALID_PRICE) {\n return (fallbackOraclePrice, false);\n }\n return (\n fallbackOraclePrice,\n boundValidator.validatePriceWithAnchorPrice(asset, fallbackOraclePrice, pivotPrice)\n );\n } catch {\n return (INVALID_PRICE, false);\n }\n }\n\n return (INVALID_PRICE, false);\n }\n\n /**\n * @dev This function returns the underlying asset of a vToken\n * @param vToken vToken address\n * @return asset underlying asset address\n */\n function _getUnderlyingAsset(address vToken) private view notNullAddress(vToken) returns (address asset) {\n if (vToken == nativeMarket) {\n asset = NATIVE_TOKEN_ADDR;\n } else if (vToken == vai) {\n asset = vai;\n } else {\n asset = VBep20Interface(vToken).underlying();\n }\n }\n\n /**\n * @dev This function checks if the asset price should be cached\n * @param asset asset address\n * @return bool true if caching is enabled, false otherwise\n */\n function _isCacheEnabled(address asset) private view returns (bool) {\n return tokenConfigs[asset].cachingEnabled;\n }\n}\n" + }, + "contracts/test/BEP20Harness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract BEP20Harness is ERC20 {\n uint8 public decimalsInternal = 18;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {\n decimalsInternal = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function decimals() public view virtual override returns (uint8) {\n return decimalsInternal;\n }\n}\n" + }, + "contracts/test/DeviationBoundedOracleCaller.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { IDeviationBoundedOracle } from \"../interfaces/IDeviationBoundedOracle.sol\";\n\n/// @notice Test helper that batches DeviationBoundedOracle calls in a single transaction\n/// so transient storage (tstore/tload) cache can be tested.\ncontract DeviationBoundedOracleCaller {\n IDeviationBoundedOracle public immutable oracle;\n\n constructor(address _oracle) {\n oracle = IDeviationBoundedOracle(_oracle);\n }\n\n function updateAndGetCollateralPrice(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedCollateralPriceView(vToken);\n }\n\n function updateAndGetDebtPrice(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedDebtPriceView(vToken);\n }\n\n function updateAndGetBothPrices(address vToken) external returns (uint256 collateral, uint256 debt) {\n oracle.updateProtectionState(vToken);\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n function getViewPricesWithoutUpdate(address vToken) external view returns (uint256 collateral, uint256 debt) {\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n function updateThenNonViewCollateral(address vToken) external returns (uint256) {\n oracle.updateProtectionState(vToken);\n return oracle.getBoundedCollateralPrice(vToken);\n }\n\n function twoConsecutiveNonViewCollateral(address vToken) external returns (uint256 first, uint256 second) {\n first = oracle.getBoundedCollateralPrice(vToken);\n second = oracle.getBoundedCollateralPrice(vToken);\n }\n\n /// @notice Non-view wrapper so smock records oracle calls made by the view functions.\n function getViewPricesWithoutUpdateNonView(address vToken) external returns (uint256 collateral, uint256 debt) {\n collateral = oracle.getBoundedCollateralPriceView(vToken);\n debt = oracle.getBoundedDebtPriceView(vToken);\n }\n\n /// @notice Calls updateProtectionState on vTokenA, then getBoundedCollateralPriceView on vTokenB.\n function updateAViewB(address vTokenA, address vTokenB) external returns (uint256) {\n oracle.updateProtectionState(vTokenA);\n return oracle.getBoundedCollateralPriceView(vTokenB);\n }\n}\n" + }, + "contracts/test/MockAnkrBNB.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IAnkrBNB } from \"../interfaces/IAnkrBNB.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAnkrBNB is ERC20, Ownable, IAnkrBNB {\n uint8 private immutable _decimals;\n uint256 public exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setSharesToBonds(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function sharesToBonds(uint256 amount) external view override returns (uint256) {\n return (amount * exchangeRate) / (10 ** uint256(_decimals));\n }\n\n function decimals() public view virtual override(ERC20, IAnkrBNB) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockAsBNB.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IAsBNB } from \"../interfaces/IAsBNB.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockAsBNB is ERC20, Ownable, IAsBNB {\n uint8 private immutable _decimals;\n address public minter;\n\n constructor(\n string memory name_,\n string memory symbol_,\n uint8 decimals_,\n address minter_\n ) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n minter = minter_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setMinter(address minter_) external onlyOwner {\n minter = minter_;\n }\n\n function decimals() public view virtual override(ERC20, IAsBNB) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockAsBNBMinter.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { IAsBNBMinter } from \"../interfaces/IAsBNBMinter.sol\";\n\ncontract MockAsBNBMinter is IAsBNBMinter {\n function convertToTokens(uint256 _amount) external pure override returns (uint256) {\n return _amount;\n }\n}\n" + }, + "contracts/test/MockCallPrice.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { OracleInterface, ResilientOracleInterface } from \"../interfaces/OracleInterface.sol\";\n\ninterface CorrelatedTokenOracleInterface {\n function updateSnapshot() external;\n function getPrice(address asset) external view returns (uint256);\n}\n\ncontract MockCallPrice {\n function getMultiPrice(CorrelatedTokenOracleInterface oracle, address asset) public returns (uint256, uint256) {\n oracle.updateSnapshot();\n return (oracle.getPrice(asset), oracle.getPrice(asset));\n }\n\n function getUnderlyingPriceResilientOracle(\n ResilientOracleInterface oracle,\n address vToken\n ) public returns (uint256, uint256) {\n oracle.updatePrice(vToken);\n oracle.updatePrice(vToken);\n return (oracle.getUnderlyingPrice(vToken), oracle.getUnderlyingPrice(vToken));\n }\n\n function getAssetPriceResilientOracle(\n ResilientOracleInterface oracle,\n address asset\n ) public returns (uint256, uint256) {\n oracle.updateAssetPrice(asset);\n oracle.updateAssetPrice(asset);\n return (oracle.getPrice(asset), oracle.getPrice(asset));\n }\n}\n" + }, + "contracts/test/MockEtherFiLiquidityPool.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/IEtherFiLiquidityPool.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockEtherFiLiquidityPool is IEtherFiLiquidityPool, Ownable {\n /// @notice The amount of eETH per weETH scaled by 1e18\n uint256 public amountPerShare;\n\n constructor() Ownable() {}\n\n function setAmountPerShare(uint256 _amountPerShare) external onlyOwner {\n amountPerShare = _amountPerShare;\n }\n\n function amountForShare(uint256 _share) external view override returns (uint256) {\n return (_share * amountPerShare) / 1e18;\n }\n}\n" + }, + "contracts/test/MockSFrax.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { ISFrax } from \"../interfaces/ISFrax.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockSFrax is ERC20, Ownable, ISFrax {\n uint8 private immutable _decimals;\n uint256 public exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setRate(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function convertToAssets(uint256 shares) external view override returns (uint256) {\n return (shares * exchangeRate) / (10 ** uint256(_decimals));\n }\n\n function decimals() public view virtual override(ERC20, ISFrax) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/MockSimpleOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../interfaces/OracleInterface.sol\";\n\ncontract MockSimpleOracle is OracleInterface {\n mapping(address => uint256) public prices;\n\n constructor() {\n //\n }\n\n function getUnderlyingPrice(address vToken) external view returns (uint256) {\n return prices[vToken];\n }\n\n function getPrice(address asset) external view returns (uint256) {\n return prices[asset];\n }\n\n function setPrice(address vToken, uint256 price) public {\n prices[vToken] = price;\n }\n}\n\ncontract MockBoundValidator is BoundValidatorInterface {\n mapping(address => bool) public validateResults;\n bool public twapUpdated;\n\n constructor() {\n //\n }\n\n function validatePriceWithAnchorPrice(\n address vToken,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool) {\n return validateResults[vToken];\n }\n\n function validateAssetPriceWithAnchorPrice(\n address asset,\n uint256 reporterPrice,\n uint256 anchorPrice\n ) external view returns (bool) {\n return validateResults[asset];\n }\n\n function setValidateResult(address token, bool pass) public {\n validateResults[token] = pass;\n }\n}\n" + }, + "contracts/test/MockV3Aggregator.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol\";\n\n/**\n * @title MockV3Aggregator\n * @notice Based on the FluxAggregator contract\n * @notice Use this contract when you need to test\n * other contract's ability to read data from an\n * aggregator contract, but how the aggregator got\n * its answer is unimportant\n */\ncontract MockV3Aggregator is AggregatorV2V3Interface {\n uint256 public constant version = 0;\n\n uint8 public decimals;\n int256 public latestAnswer;\n uint256 public latestTimestamp;\n uint256 public latestRound;\n\n mapping(uint256 => int256) public getAnswer;\n mapping(uint256 => uint256) public getTimestamp;\n mapping(uint256 => uint256) private getStartedAt;\n\n constructor(uint8 _decimals, int256 _initialAnswer) {\n decimals = _decimals;\n updateAnswer(_initialAnswer);\n }\n\n function getRoundData(\n uint80 _roundId\n )\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId);\n }\n\n function latestRoundData()\n external\n view\n returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)\n {\n return (\n uint80(latestRound),\n getAnswer[latestRound],\n getStartedAt[latestRound],\n getTimestamp[latestRound],\n uint80(latestRound)\n );\n }\n\n function description() external pure returns (string memory) {\n return \"v0.6/tests/MockV3Aggregator.sol\";\n }\n\n function updateAnswer(int256 _answer) public {\n latestAnswer = _answer;\n latestTimestamp = block.timestamp;\n latestRound++;\n getAnswer[latestRound] = _answer;\n getTimestamp[latestRound] = block.timestamp;\n getStartedAt[latestRound] = block.timestamp;\n }\n\n function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public {\n latestRound = _roundId;\n latestAnswer = _answer;\n latestTimestamp = _timestamp;\n getAnswer[latestRound] = _answer;\n getTimestamp[latestRound] = _timestamp;\n getStartedAt[latestRound] = _startedAt;\n }\n}\n" + }, + "contracts/test/MockWBETH.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { IWBETH } from \"../interfaces/IWBETH.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\n\ncontract MockWBETH is ERC20, Ownable, IWBETH {\n uint8 private immutable _decimals;\n uint256 public override exchangeRate;\n\n constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) Ownable() {\n _decimals = decimals_;\n }\n\n function faucet(uint256 amount) external {\n _mint(msg.sender, amount);\n }\n\n function setExchangeRate(uint256 rate) external onlyOwner {\n exchangeRate = rate;\n }\n\n function decimals() public view virtual override(ERC20, IWBETH) returns (uint8) {\n return _decimals;\n }\n}\n" + }, + "contracts/test/oracles/MockCorrelatedTokenOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport { CorrelatedTokenOracle } from \"../../oracles/common/CorrelatedTokenOracle.sol\";\n\ncontract MockCorrelatedTokenOracle is CorrelatedTokenOracle {\n uint256 public mockUnderlyingAmount;\n\n constructor(\n address correlatedToken,\n address underlyingToken,\n address resilientOracle,\n uint256 annualGrowthRate,\n uint256 snapshotInterval,\n uint256 initialSnapshotMaxExchangeRate,\n uint256 initialSnapshotTimestamp,\n address accessControlManager,\n uint256 snapshotGap\n )\n CorrelatedTokenOracle(\n correlatedToken,\n underlyingToken,\n resilientOracle,\n annualGrowthRate,\n snapshotInterval,\n initialSnapshotMaxExchangeRate,\n initialSnapshotTimestamp,\n accessControlManager,\n snapshotGap\n )\n {}\n\n function setMockUnderlyingAmount(uint256 amount) external {\n mockUnderlyingAmount = amount;\n }\n\n function getUnderlyingAmount() public view override returns (uint256) {\n return mockUnderlyingAmount;\n }\n}\n" + }, + "contracts/test/oracles/MockERC20.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract MockERC20 is ERC20 {\n constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol) {\n _mint(msg.sender, 100000 * 10 ** uint256(decimals));\n }\n}\n" + }, + "contracts/test/oracles/MockResilientOracle.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"../../interfaces/OracleInterface.sol\";\n\ncontract MockOracle is OracleInterface {\n mapping(address => uint256) public prices;\n\n function getPrice(address asset) external view returns (uint256) {\n return prices[asset];\n }\n\n function setPrice(address vToken, uint256 price) public {\n prices[vToken] = price;\n }\n}\n" + }, + "contracts/test/PancakePairHarness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\n// a library for performing various math operations\n\nlibrary Math {\n function min(uint256 x, uint256 y) internal pure returns (uint256 z) {\n z = x < y ? x : y;\n }\n\n // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)\n function sqrt(uint256 y) internal pure returns (uint256 z) {\n if (y > 3) {\n z = y;\n uint256 x = y / 2 + 1;\n while (x < z) {\n z = x;\n x = (y / x + x) / 2;\n }\n } else if (y != 0) {\n z = 1;\n }\n }\n}\n\n// range: [0, 2**112 - 1]\n// resolution: 1 / 2**112\n\nlibrary UQ112x112 {\n //solhint-disable-next-line state-visibility\n uint224 constant Q112 = 2 ** 112;\n\n // encode a uint112 as a UQ112x112\n function encode(uint112 y) internal pure returns (uint224 z) {\n z = uint224(y) * Q112; // never overflows\n }\n\n // divide a UQ112x112 by a uint112, returning a UQ112x112\n function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {\n z = x / uint224(y);\n }\n}\n\ncontract PancakePairHarness {\n using UQ112x112 for uint224;\n\n address public token0;\n address public token1;\n\n uint112 private reserve0; // uses single storage slot, accessible via getReserves\n uint112 private reserve1; // uses single storage slot, accessible via getReserves\n uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves\n\n uint256 public price0CumulativeLast;\n uint256 public price1CumulativeLast;\n uint256 public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event\n\n // called once by the factory at time of deployment\n function initialize(address _token0, address _token1) external {\n token0 = _token0;\n token1 = _token1;\n }\n\n // update reserves and, on the first call per block, price accumulators\n function update(uint256 balance0, uint256 balance1, uint112 _reserve0, uint112 _reserve1) external {\n require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, \"PancakeV2: OVERFLOW\");\n uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);\n unchecked {\n uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired\n if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {\n // * never overflows, and + overflow is desired\n price0CumulativeLast += uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;\n price1CumulativeLast += uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;\n }\n }\n reserve0 = uint112(balance0);\n reserve1 = uint112(balance1);\n blockTimestampLast = blockTimestamp;\n }\n\n function currentBlockTimestamp() external view returns (uint32) {\n return uint32(block.timestamp % 2 ** 32);\n }\n\n function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {\n _reserve0 = reserve0;\n _reserve1 = reserve1;\n _blockTimestampLast = blockTimestampLast;\n }\n}\n" + }, + "contracts/test/VBEP20Harness.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\npragma solidity 0.8.25;\n\nimport \"./BEP20Harness.sol\";\n\ncontract VBEP20Harness is BEP20Harness {\n /**\n * @notice Underlying asset for this VToken\n */\n address public underlying;\n\n constructor(\n string memory name_,\n string memory symbol_,\n uint8 decimals,\n address underlying_\n ) BEP20Harness(name_, symbol_, decimals) {\n underlying = underlying_;\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor (address initialOwner) {\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/interfaces/draft-IERC1822.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (interfaces/draft-IERC1822.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified\n * proxy whose upgrades are fully controlled by the current implementation.\n */\ninterface IERC1822Proxiable {\n /**\n * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation\n * address.\n *\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\n * function revert if invoked through a proxy.\n */\n function proxiableUUID() external view returns (bytes32);\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/beacon/IBeacon.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev This is the interface that {BeaconProxy} expects of its beacon.\n */\ninterface IBeacon {\n /**\n * @dev Must return an address that can be used as a delegate call target.\n *\n * {BeaconProxy} will check that this address is a contract.\n */\n function implementation() external view returns (address);\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Proxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../Proxy.sol\";\nimport \"./ERC1967Upgrade.sol\";\n\n/**\n * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\n * implementation address that can be changed. This address is stored in storage in the location specified by\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the\n * implementation behind the proxy.\n */\ncontract ERC1967Proxy is Proxy, ERC1967Upgrade {\n /**\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.\n *\n * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded\n * function call, and allows initializating the storage of the proxy like a Solidity constructor.\n */\n constructor(address _logic, bytes memory _data) payable {\n assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.implementation\")) - 1));\n _upgradeToAndCall(_logic, _data, false);\n }\n\n /**\n * @dev Returns the current implementation address.\n */\n function _implementation() internal view virtual override returns (address impl) {\n return ERC1967Upgrade._getImplementation();\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/ERC1967/ERC1967Upgrade.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/ERC1967/ERC1967Upgrade.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../beacon/IBeacon.sol\";\nimport \"../../interfaces/draft-IERC1822.sol\";\nimport \"../../utils/Address.sol\";\nimport \"../../utils/StorageSlot.sol\";\n\n/**\n * @dev This abstract contract provides getters and event emitting update functions for\n * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.\n *\n * _Available since v4.1._\n *\n * @custom:oz-upgrades-unsafe-allow delegatecall\n */\nabstract contract ERC1967Upgrade {\n // This is the keccak-256 hash of \"eip1967.proxy.rollback\" subtracted by 1\n bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;\n\n /**\n * @dev Storage slot with the address of the current implementation.\n * This is the keccak-256 hash of \"eip1967.proxy.implementation\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n\n /**\n * @dev Emitted when the implementation is upgraded.\n */\n event Upgraded(address indexed implementation);\n\n /**\n * @dev Returns the current implementation address.\n */\n function _getImplementation() internal view returns (address) {\n return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n }\n\n /**\n * @dev Stores a new address in the EIP1967 implementation slot.\n */\n function _setImplementation(address newImplementation) private {\n require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n }\n\n /**\n * @dev Perform implementation upgrade\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeTo(address newImplementation) internal {\n _setImplementation(newImplementation);\n emit Upgraded(newImplementation);\n }\n\n /**\n * @dev Perform implementation upgrade with additional setup call.\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeToAndCall(\n address newImplementation,\n bytes memory data,\n bool forceCall\n ) internal {\n _upgradeTo(newImplementation);\n if (data.length > 0 || forceCall) {\n Address.functionDelegateCall(newImplementation, data);\n }\n }\n\n /**\n * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.\n *\n * Emits an {Upgraded} event.\n */\n function _upgradeToAndCallUUPS(\n address newImplementation,\n bytes memory data,\n bool forceCall\n ) internal {\n // Upgrades from old implementations will perform a rollback test. This test requires the new\n // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing\n // this special case will break upgrade paths from old UUPS implementation to new ones.\n if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {\n _setImplementation(newImplementation);\n } else {\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\n require(slot == _IMPLEMENTATION_SLOT, \"ERC1967Upgrade: unsupported proxiableUUID\");\n } catch {\n revert(\"ERC1967Upgrade: new implementation is not UUPS\");\n }\n _upgradeToAndCall(newImplementation, data, forceCall);\n }\n }\n\n /**\n * @dev Storage slot with the admin of the contract.\n * This is the keccak-256 hash of \"eip1967.proxy.admin\" subtracted by 1, and is\n * validated in the constructor.\n */\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n /**\n * @dev Emitted when the admin account has changed.\n */\n event AdminChanged(address previousAdmin, address newAdmin);\n\n /**\n * @dev Returns the current admin.\n */\n function _getAdmin() internal view virtual returns (address) {\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\n }\n\n /**\n * @dev Stores a new address in the EIP1967 admin slot.\n */\n function _setAdmin(address newAdmin) private {\n require(newAdmin != address(0), \"ERC1967: new admin is the zero address\");\n StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;\n }\n\n /**\n * @dev Changes the admin of the proxy.\n *\n * Emits an {AdminChanged} event.\n */\n function _changeAdmin(address newAdmin) internal {\n emit AdminChanged(_getAdmin(), newAdmin);\n _setAdmin(newAdmin);\n }\n\n /**\n * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.\n * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.\n */\n bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;\n\n /**\n * @dev Emitted when the beacon is upgraded.\n */\n event BeaconUpgraded(address indexed beacon);\n\n /**\n * @dev Returns the current beacon.\n */\n function _getBeacon() internal view returns (address) {\n return StorageSlot.getAddressSlot(_BEACON_SLOT).value;\n }\n\n /**\n * @dev Stores a new beacon in the EIP1967 beacon slot.\n */\n function _setBeacon(address newBeacon) private {\n require(Address.isContract(newBeacon), \"ERC1967: new beacon is not a contract\");\n require(Address.isContract(IBeacon(newBeacon).implementation()), \"ERC1967: beacon implementation is not a contract\");\n StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;\n }\n\n /**\n * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does\n * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).\n *\n * Emits a {BeaconUpgraded} event.\n */\n function _upgradeBeaconToAndCall(\n address newBeacon,\n bytes memory data,\n bool forceCall\n ) internal {\n _setBeacon(newBeacon);\n emit BeaconUpgraded(newBeacon);\n if (data.length > 0 || forceCall) {\n Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/Proxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (proxy/Proxy.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM\n * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to\n * be specified by overriding the virtual {_implementation} function.\n *\n * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a\n * different contract through the {_delegate} function.\n *\n * The success and return data of the delegated call will be returned back to the caller of the proxy.\n */\nabstract contract Proxy {\n /**\n * @dev Delegates the current call to `implementation`.\n *\n * This function does not return to its internal call site, it will return directly to the external caller.\n */\n function _delegate(address implementation) internal virtual {\n assembly {\n // Copy msg.data. We take full control of memory in this inline assembly\n // block because it will not return to Solidity code. We overwrite the\n // Solidity scratch pad at memory position 0.\n calldatacopy(0, 0, calldatasize())\n\n // Call the implementation.\n // out and outsize are 0 because we don't know the size yet.\n let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)\n\n // Copy the returned data.\n returndatacopy(0, 0, returndatasize())\n\n switch result\n // delegatecall returns 0 on error.\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n\n /**\n * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function\n * and {_fallback} should delegate.\n */\n function _implementation() internal view virtual returns (address);\n\n /**\n * @dev Delegates the current call to the address returned by `_implementation()`.\n *\n * This function does not return to its internall call site, it will return directly to the external caller.\n */\n function _fallback() internal virtual {\n _beforeFallback();\n _delegate(_implementation());\n }\n\n /**\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other\n * function in the contract matches the call data.\n */\n fallback() external payable virtual {\n _fallback();\n }\n\n /**\n * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data\n * is empty.\n */\n receive() external payable virtual {\n _fallback();\n }\n\n /**\n * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`\n * call, or as part of the Solidity `fallback` or `receive` functions.\n *\n * If overriden should call `super._beforeFallback()`.\n */\n function _beforeFallback() internal virtual {}\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/ProxyAdmin.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./TransparentUpgradeableProxy.sol\";\nimport \"../../access/Ownable.sol\";\n\n/**\n * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an\n * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.\n */\ncontract ProxyAdmin is Ownable {\n\n constructor (address initialOwner) Ownable(initialOwner) {}\n\n /**\n * @dev Returns the current implementation of `proxy`.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {\n // We need to manually run the static call since the getter cannot be flagged as view\n // bytes4(keccak256(\"implementation()\")) == 0x5c60da1b\n (bool success, bytes memory returndata) = address(proxy).staticcall(hex\"5c60da1b\");\n require(success);\n return abi.decode(returndata, (address));\n }\n\n /**\n * @dev Returns the current admin of `proxy`.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {\n // We need to manually run the static call since the getter cannot be flagged as view\n // bytes4(keccak256(\"admin()\")) == 0xf851a440\n (bool success, bytes memory returndata) = address(proxy).staticcall(hex\"f851a440\");\n require(success);\n return abi.decode(returndata, (address));\n }\n\n /**\n * @dev Changes the admin of `proxy` to `newAdmin`.\n *\n * Requirements:\n *\n * - This contract must be the current admin of `proxy`.\n */\n function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {\n proxy.changeAdmin(newAdmin);\n }\n\n /**\n * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {\n proxy.upgradeTo(implementation);\n }\n\n /**\n * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See\n * {TransparentUpgradeableProxy-upgradeToAndCall}.\n *\n * Requirements:\n *\n * - This contract must be the admin of `proxy`.\n */\n function upgradeAndCall(\n TransparentUpgradeableProxy proxy,\n address implementation,\n bytes memory data\n ) public payable virtual onlyOwner {\n proxy.upgradeToAndCall{value: msg.value}(implementation, data);\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../ERC1967/ERC1967Proxy.sol\";\n\n/**\n * @dev This contract implements a proxy that is upgradeable by an admin.\n *\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\n * clashing], which can potentially be used in an attack, this contract uses the\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\n * things that go hand in hand:\n *\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\n * that call matches one of the admin functions exposed by the proxy itself.\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\n * \"admin cannot fallback to proxy target\".\n *\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\n * to sudden errors when trying to call a function from the proxy implementation.\n *\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\n */\ncontract TransparentUpgradeableProxy is ERC1967Proxy {\n /**\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\n */\n constructor(\n address _logic,\n address admin_,\n bytes memory _data\n ) payable ERC1967Proxy(_logic, _data) {\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n _changeAdmin(admin_);\n }\n\n /**\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\n */\n modifier ifAdmin() {\n if (msg.sender == _getAdmin()) {\n _;\n } else {\n _fallback();\n }\n }\n\n /**\n * @dev Returns the current admin.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\n */\n function admin() external ifAdmin returns (address admin_) {\n admin_ = _getAdmin();\n }\n\n /**\n * @dev Returns the current implementation.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\n */\n function implementation() external ifAdmin returns (address implementation_) {\n implementation_ = _implementation();\n }\n\n /**\n * @dev Changes the admin of the proxy.\n *\n * Emits an {AdminChanged} event.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.\n */\n function changeAdmin(address newAdmin) external virtual ifAdmin {\n _changeAdmin(newAdmin);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\n */\n function upgradeTo(address newImplementation) external ifAdmin {\n _upgradeToAndCall(newImplementation, bytes(\"\"), false);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\n * proxied contract.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\n _upgradeToAndCall(newImplementation, data, true);\n }\n\n /**\n * @dev Returns the current admin.\n */\n function _admin() internal view virtual returns (address) {\n return _getAdmin();\n }\n\n /**\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\n */\n function _beforeFallback() internal virtual override {\n require(msg.sender != _getAdmin(), \"TransparentUpgradeableProxy: admin cannot fallback to proxy target\");\n super._beforeFallback();\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0-rc.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/openzeppelin/utils/StorageSlot.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC1967 implementation slot:\n * ```\n * contract ERC1967 {\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n}\n" + }, + "hardhat-deploy/solc_0.8/proxy/OptimizedTransparentUpgradeableProxy.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../openzeppelin/proxy/ERC1967/ERC1967Proxy.sol\";\n\n/**\n * @dev This contract implements a proxy that is upgradeable by an admin.\n *\n * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector\n * clashing], which can potentially be used in an attack, this contract uses the\n * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two\n * things that go hand in hand:\n *\n * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if\n * that call matches one of the admin functions exposed by the proxy itself.\n * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the\n * implementation. If the admin tries to call a function on the implementation it will fail with an error that says\n * \"admin cannot fallback to proxy target\".\n *\n * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing\n * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due\n * to sudden errors when trying to call a function from the proxy implementation.\n *\n * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,\n * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.\n */\ncontract OptimizedTransparentUpgradeableProxy is ERC1967Proxy {\n address internal immutable _ADMIN;\n\n /**\n * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and\n * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.\n */\n constructor(\n address _logic,\n address admin_,\n bytes memory _data\n ) payable ERC1967Proxy(_logic, _data) {\n assert(_ADMIN_SLOT == bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n _ADMIN = admin_;\n\n // still store it to work with EIP-1967\n bytes32 slot = _ADMIN_SLOT;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n sstore(slot, admin_)\n }\n emit AdminChanged(address(0), admin_);\n }\n\n /**\n * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.\n */\n modifier ifAdmin() {\n if (msg.sender == _getAdmin()) {\n _;\n } else {\n _fallback();\n }\n }\n\n /**\n * @dev Returns the current admin.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`\n */\n function admin() external ifAdmin returns (address admin_) {\n admin_ = _getAdmin();\n }\n\n /**\n * @dev Returns the current implementation.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.\n *\n * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the\n * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`\n */\n function implementation() external ifAdmin returns (address implementation_) {\n implementation_ = _implementation();\n }\n\n /**\n * @dev Upgrade the implementation of the proxy.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.\n */\n function upgradeTo(address newImplementation) external ifAdmin {\n _upgradeToAndCall(newImplementation, bytes(\"\"), false);\n }\n\n /**\n * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified\n * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the\n * proxied contract.\n *\n * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.\n */\n function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {\n _upgradeToAndCall(newImplementation, data, true);\n }\n\n /**\n * @dev Returns the current admin.\n */\n function _admin() internal view virtual returns (address) {\n return _getAdmin();\n }\n\n /**\n * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.\n */\n function _beforeFallback() internal virtual override {\n require(msg.sender != _getAdmin(), \"TransparentUpgradeableProxy: admin cannot fallback to proxy target\");\n super._beforeFallback();\n }\n\n function _getAdmin() internal view virtual override returns (address) {\n return _ADMIN;\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200, + "details": { + "yul": true + } + }, + "evmVersion": "cancun", + "outputSelection": { + "*": { + "*": [ + "storageLayout", + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "evm.gasEstimates" + ], + "": ["ast"] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} diff --git a/deployments/bsctestnet_addresses.json b/deployments/bsctestnet_addresses.json index d6de5c58..61e14359 100644 --- a/deployments/bsctestnet_addresses.json +++ b/deployments/bsctestnet_addresses.json @@ -15,6 +15,9 @@ "ChainlinkOracle_Implementation": "0xBea89b7560Ec88f75f31A8E62da7F2d52c807416", "ChainlinkOracle_Proxy": "0xCeA29f1266e880A1482c06eD656cD08C148BaA32", "DefaultProxyAdmin": "0xef480a5654b231ff7d80A0681F938f3Db71a6Ca6", + "DeviationBoundedOracle": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", + "DeviationBoundedOracle_Implementation": "0x90c9756446ebA9E1762811c239Fe10029019e35e", + "DeviationBoundedOracle_Proxy": "0xE0dafC97895B3c98d3B96D3f8739AaC73166beB8", "MockAnkrBNB": "0x5269b7558D3d5E113010Ef1cFF0901c367849CC9", "MockAsBNB": "0xc625f060ad25f4A6c2d9eBF30C133dB61B7AF072", "MockAsBNBMinter": "0x0E35fe8594551Eba23f7BC191De20612a0f152F4", diff --git a/hardhat.config.ts b/hardhat.config.ts index 70a88519..b9337e0d 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -41,6 +41,16 @@ function isFork() { }, live: false, saveDeployments: false, + chains: { + 56: { + hardforkHistory: { + berlin: 0, + london: 13_000_000, + shanghai: 39_539_137, + cancun: 40_790_075, + }, + }, + }, } : { allowUnlimitedContractSize: true, diff --git a/scripts/checkGuardianAccess.ts b/scripts/checkGuardianAccess.ts new file mode 100644 index 00000000..8c3f574c --- /dev/null +++ b/scripts/checkGuardianAccess.ts @@ -0,0 +1,44 @@ +import { ethers } from "hardhat"; + +const RESILIENT_ORACLE = "0xDe564a4C887d5ad315a19a96DC81991c98b12182"; +const ACM = "0x526159A92A82afE5327d37Ef446b68FD9a5cA914"; + +const ACCOUNTS = { + Guardian: "0x751Aa759cfBB6CE71A43b48e40e1cCcFC66Ba4aa", + "Normal Timelock": "0x093565Bc20AA326F4209eBaF3a26089272627613", + "Fasttrack Timelock": "0x32f71c95BC8F9d996f89c642f1a84d06B2484AE9", + "Critical Timelock": "0xbfbc79D4198963e4a66270F3EfB1fdA0F382E49c", +}; + +const ACM_ABI = ["function hasRole(bytes32 role, address account) external view returns (bool)"]; + +const FUNCTIONS = [ + "setTokenConfig(TokenConfig)", + "setOracle(address,address,uint8)", + "enableOracle(address,uint8,bool)", + "pause()", + "unpause()", +]; + +async function main() { + const acm = new ethers.Contract(ACM, ACM_ABI, ethers.provider); + + console.log(`ResilientOracle: ${RESILIENT_ORACLE}`); + console.log(`ACM: ${ACM}\n`); + + for (const [name, address] of Object.entries(ACCOUNTS)) { + console.log(`--- ${name} (${address}) ---`); + for (const fn of FUNCTIONS) { + // Replicate ACM logic: role = keccak256(abi.encodePacked(callingContract, functionSig)) + const role = ethers.utils.keccak256(ethers.utils.solidityPack(["address", "string"], [RESILIENT_ORACLE, fn])); + const allowed = await acm.hasRole(role, address); + console.log(` ${allowed ? "✅" : "❌"} ${fn}`); + } + console.log(); + } +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/test/DeviationBoundedOracle.ts b/test/DeviationBoundedOracle.ts new file mode 100644 index 00000000..942e8295 --- /dev/null +++ b/test/DeviationBoundedOracle.ts @@ -0,0 +1,2111 @@ +import { FakeContract, smock } from "@defi-wonderland/smock"; +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; +import chai from "chai"; +import { BigNumber } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers, upgrades } from "hardhat"; + +import { + AccessControlManager, + DeviationBoundedOracle, + DeviationBoundedOracle__factory, + ResilientOracle, + VBEP20Harness, +} from "../typechain-types"; +import { addr0000 } from "./utils/data"; +import { makeVToken } from "./utils/makeVToken"; + +const { expect } = chai; +chai.use(smock.matchers); + +const EXP_SCALE = parseUnits("1", 18); +const MIN_THRESHOLD = parseUnits("0.05", 18); // 5% +const MAX_THRESHOLD = parseUnits("0.5", 18); // 50% +const KEEPER_DEADBAND = parseUnits("0.05", 18); // 5% +const DEFAULT_THRESHOLD = parseUnits("0.2", 18); // 20% +const DEFAULT_RESET_THRESHOLD = parseUnits("0.1", 18); // 10% +const DEFAULT_COOLDOWN = 3600; // 1 hour +const SPOT_PRICE = parseUnits("1", 18); // 1e18 +const MIN_PRICE = parseUnits("0.9", 18); +const MAX_PRICE = parseUnits("1.1", 18); +const NATIVE_TOKEN_ADDR = "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"; + +describe("DeviationBoundedOracle", () => { + let admin: SignerWithAddress; + let someone: SignerWithAddress; + let resilientOracle: FakeContract; + let acm: FakeContract; + let oracle: DeviationBoundedOracle; + let oracleFactory: DeviationBoundedOracle__factory; + let vTokenA: VBEP20Harness; + let vTokenB: VBEP20Harness; + let assetA: string; + let assetB: string; + let nativeMarket: VBEP20Harness; + let vaiToken: VBEP20Harness; + + const tokenCfg = ( + asset: string, + cooldownPeriod: number | BigNumber = DEFAULT_COOLDOWN, + triggerThreshold: BigNumber = DEFAULT_THRESHOLD, + resetThreshold: BigNumber = DEFAULT_RESET_THRESHOLD, + enableBoundedPricing = true, + enableCaching = true, + ) => ({ + asset, + cooldownPeriod, + triggerThreshold, + resetThreshold, + enableBoundedPricing, + enableCaching, + }); + + // Basic init: min=max=spot (SPOT_PRICE = 1e18) + const initAsset = async ( + asset: string, + cooldown: number = DEFAULT_COOLDOWN, + triggerThreshold: BigNumber = DEFAULT_THRESHOLD, + resetThreshold: BigNumber = DEFAULT_RESET_THRESHOLD, + ) => { + await oracle.setTokenConfig(tokenCfg(asset, cooldown, triggerThreshold, resetThreshold)); + }; + + // Init + widen window via keeper updates (for price-bounding tests that need min=0.9, max=1.1) + const initAssetWithWindow = async ( + asset: string, + minPrice: BigNumber = MIN_PRICE, + maxPrice: BigNumber = MAX_PRICE, + cooldown: number = DEFAULT_COOLDOWN, + triggerThreshold: BigNumber = DEFAULT_THRESHOLD, + resetThreshold: BigNumber = DEFAULT_RESET_THRESHOLD, + ) => { + await oracle.setTokenConfig(tokenCfg(asset, cooldown, triggerThreshold, resetThreshold)); + await oracle.updateMinPrice(asset, minPrice); + await oracle.updateMaxPrice(asset, maxPrice); + }; + + before(async () => { + [admin, someone] = await ethers.getSigners(); + oracleFactory = await ethers.getContractFactory("DeviationBoundedOracle", admin); + resilientOracle = await smock.fake("ResilientOracle"); + acm = await smock.fake("AccessControlManager"); + acm.isAllowedToCall.returns(true); + + vTokenA = await makeVToken({ name: "vTokenA", symbol: "vTKA" }, { name: "TokenA", symbol: "TKA", decimals: 18 }); + vTokenB = await makeVToken({ name: "vTokenB", symbol: "vTKB" }, { name: "TokenB", symbol: "TKB", decimals: 18 }); + nativeMarket = await makeVToken( + { name: "vNative", symbol: "vNAT" }, + { name: "Native", symbol: "NAT", decimals: 18 }, + ); + vaiToken = await makeVToken({ name: "vVAI", symbol: "vVAI" }, { name: "VAI", symbol: "VAI", decimals: 18 }); + assetA = await vTokenA.underlying(); + assetB = await vTokenB.underlying(); + }); + + beforeEach(async () => { + acm.isAllowedToCall.returns(true); + resilientOracle.getPrice.reset(); + resilientOracle.getPrice.returns(SPOT_PRICE); + + oracle = await upgrades.deployProxy(oracleFactory, [acm.address], { + constructorArgs: [resilientOracle.address, nativeMarket.address, await vaiToken.underlying()], + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 1. Constructor + // ──────────────────────────────────────────────────────────────────────── + + describe("constructor", () => { + it("sets immutables correctly", async () => { + expect(await oracle.RESILIENT_ORACLE()).to.equal(resilientOracle.address); + expect(await oracle.nativeMarket()).to.equal(nativeMarket.address); + expect(await oracle.vai()).to.equal(await vaiToken.underlying()); + }); + + it("reverts when _resilientOracle is zero address", async () => { + await expect( + upgrades.deployProxy(oracleFactory, [acm.address], { + constructorArgs: [addr0000, nativeMarket.address, await vaiToken.underlying()], + }), + ).to.be.revertedWithCustomError(oracle, "ZeroAddressNotAllowed"); + }); + + it("reverts when nativeMarketAddress is zero address", async () => { + await expect( + upgrades.deployProxy(oracleFactory, [acm.address], { + constructorArgs: [resilientOracle.address, addr0000, await vaiToken.underlying()], + }), + ).to.be.revertedWithCustomError(oracle, "ZeroAddressNotAllowed"); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 2. Initialize + // ──────────────────────────────────────────────────────────────────────── + + describe("initialize", () => { + it("sets access control manager", async () => { + expect(await oracle.accessControlManager()).to.equal(acm.address); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 3. setTokenConfig + // ──────────────────────────────────────────────────────────────────────── + + describe("setTokenConfig", () => { + describe("happy path", () => { + it("sets all struct fields, emits events, updates asset lists", async () => { + const tx = await oracle.setTokenConfig(tokenCfg(assetA)); + + // Verify struct fields via public getter + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(SPOT_PRICE); + expect(state.maxPrice).to.equal(SPOT_PRICE); + expect(state.currentlyUsingProtectedPrice).to.equal(false); + expect(state.isBoundedPricingEnabled).to.equal(true); + expect(state.lastProtectionTriggeredAt).to.equal(0); + expect(state.cooldownPeriod).to.equal(DEFAULT_COOLDOWN); + expect(state.asset).to.equal(assetA); + expect(state.triggerThreshold).to.equal(DEFAULT_THRESHOLD); + expect(state.resetThreshold).to.equal(DEFAULT_RESET_THRESHOLD); + expect(state.cachingEnabled).to.equal(true); + + // Verify events + await expect(tx) + .to.emit(oracle, "ProtectionInitialized") + .withArgs(assetA, SPOT_PRICE, SPOT_PRICE, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, true); + + // Verify in getInitializedAssets + const initialized = await oracle.getInitializedAssets(); + expect(initialized).to.include(assetA); + + // Verify in getAllBoundedPricingEnabledAssets + const whitelisted = await oracle.getAllBoundedPricingEnabledAssets(); + expect(whitelisted).to.include(assetA); + }); + + it("initializes with bounded pricing disabled when enableBoundedPricing is false", async () => { + const tx = await oracle.setTokenConfig( + tokenCfg(assetA, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD, false, true), + ); + + const state = await oracle.assetProtectionConfig(assetA); + expect(state.isBoundedPricingEnabled).to.equal(false); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, false); + + // Price functions return spot (not bounded) even with deviation + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + const price = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(price).to.equal(pumpSpot); + }); + }); + + describe("revert branches", () => { + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect(oracle.setTokenConfig(tokenCfg(assetA))).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("reverts when asset is zero address", async () => { + await expect(oracle.setTokenConfig(tokenCfg(addr0000))).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("reverts when already initialized", async () => { + await initAsset(assetA); + await expect(oracle.setTokenConfig(tokenCfg(assetA))).to.be.revertedWithCustomError( + oracle, + "MarketAlreadyInitialized", + ); + }); + + it("reverts when threshold < MIN_THRESHOLD", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, MIN_THRESHOLD.sub(1), DEFAULT_RESET_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "ThresholdBelowMinimum"); + }); + + it("reverts when threshold > MAX_THRESHOLD", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, MAX_THRESHOLD.add(1), DEFAULT_RESET_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "ThresholdAboveMaximum"); + }); + + it("reverts when resetThreshold >= triggerThreshold", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD, DEFAULT_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "InvalidResetThreshold"); + }); + + it("reverts when asset is VAI", async () => { + const vaiAddr = await vaiToken.underlying(); + await expect(oracle.setTokenConfig(tokenCfg(vaiAddr))).to.be.revertedWithCustomError(oracle, "VAINotAllowed"); + }); + + it("reverts when cooldownPeriod is zero", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, 0, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "ZeroValueNotAllowed"); + }); + + it("reverts when triggerThreshold is zero", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, BigNumber.from(0), DEFAULT_RESET_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "ZeroValueNotAllowed"); + }); + + it("reverts when resetThreshold is zero", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD, BigNumber.from(0))), + ).to.be.revertedWithCustomError(oracle, "ZeroValueNotAllowed"); + }); + + it("reverts when re-initializing after de-whitelist", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + await expect(oracle.setTokenConfig(tokenCfg(assetA))).to.be.revertedWithCustomError( + oracle, + "MarketAlreadyInitialized", + ); + }); + + it("reverts with PriceExceedsUint128 when oracle returns > uint128 max", async () => { + const overflowPrice = BigNumber.from(2).pow(128); + resilientOracle.getPrice.whenCalledWith(assetA).returns(overflowPrice); + await expect(oracle.setTokenConfig(tokenCfg(assetA))).to.be.revertedWithCustomError( + oracle, + "PriceExceedsUint128", + ); + }); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 3b. setTokenConfigs (batch) + // ──────────────────────────────────────────────────────────────────────── + + describe("setTokenConfigs (batch)", () => { + const cfg = (asset: string, enableBoundedPricing = true, enableCaching = true) => ({ + asset, + cooldownPeriod: DEFAULT_COOLDOWN, + triggerThreshold: DEFAULT_THRESHOLD, + resetThreshold: DEFAULT_RESET_THRESHOLD, + enableBoundedPricing, + enableCaching, + }); + + it("batch-initializes multiple assets, verifies structs and events", async () => { + const tx = await oracle.setTokenConfigs([cfg(assetA), cfg(assetB)]); + + // Verify both assets initialized + const stateA = await oracle.assetProtectionConfig(assetA); + expect(stateA.asset).to.equal(assetA); + expect(stateA.isBoundedPricingEnabled).to.equal(true); + expect(stateA.minPrice).to.equal(SPOT_PRICE); + + const stateB = await oracle.assetProtectionConfig(assetB); + expect(stateB.asset).to.equal(assetB); + expect(stateB.isBoundedPricingEnabled).to.equal(true); + + // Verify events for both + await expect(tx) + .to.emit(oracle, "ProtectionInitialized") + .withArgs(assetA, SPOT_PRICE, SPOT_PRICE, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD); + await expect(tx) + .to.emit(oracle, "ProtectionInitialized") + .withArgs(assetB, SPOT_PRICE, SPOT_PRICE, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, true); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetB, true); + + const initialized = await oracle.getInitializedAssets(); + expect(initialized.length).to.equal(2); + }); + + it("batch with mixed enableBoundedPricing values", async () => { + await oracle.setTokenConfigs([cfg(assetA, true, true), cfg(assetB, false, true)]); + + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(true); + expect(await oracle.isBoundedPricingEnabled(assetB)).to.equal(false); + }); + + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect(oracle.setTokenConfigs([cfg(assetA)])).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("reverts when one asset in batch is invalid (entire tx reverts)", async () => { + await expect(oracle.setTokenConfigs([cfg(assetA), cfg(addr0000)])).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("reverts when one asset is already initialized", async () => { + await initAsset(assetA); + await expect(oracle.setTokenConfigs([cfg(assetA), cfg(assetB)])).to.be.revertedWithCustomError( + oracle, + "MarketAlreadyInitialized", + ); + }); + + it("reverts on empty input", async () => { + await expect(oracle.setTokenConfigs([])).to.be.revertedWithCustomError(oracle, "InvalidArrayLength"); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 4. setCooldownPeriod + // ──────────────────────────────────────────────────────────────────────── + + describe("setCooldownPeriod", () => { + beforeEach(async () => { + await initAsset(assetA); + }); + + it("updates cooldownPeriod and emits event", async () => { + const newCooldown = 7200; + const tx = await oracle.setCooldownPeriod(assetA, newCooldown); + await expect(tx).to.emit(oracle, "CooldownPeriodSet").withArgs(assetA, DEFAULT_COOLDOWN, newCooldown); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.cooldownPeriod).to.equal(newCooldown); + }); + + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect(oracle.setCooldownPeriod(assetA, 7200)).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("reverts when asset is zero address", async () => { + await expect(oracle.setCooldownPeriod(addr0000, 7200)).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("reverts when not initialized", async () => { + await expect(oracle.setCooldownPeriod(assetB, 7200)).to.be.revertedWithCustomError( + oracle, + "MarketNotInitialized", + ); + }); + + it("reverts when new cooldown is zero", async () => { + await expect(oracle.setCooldownPeriod(assetA, 0)).to.be.revertedWithCustomError(oracle, "ZeroValueNotAllowed"); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 5. setThresholds + // ──────────────────────────────────────────────────────────────────────── + + describe("setThresholds", () => { + beforeEach(async () => { + await initAsset(assetA); + }); + + it("updates both thresholds and emits both events", async () => { + const newTrigger = parseUnits("0.25", 18); + const newReset = parseUnits("0.12", 18); + const tx = await oracle.setThresholds(assetA, newTrigger, newReset); + await expect(tx).to.emit(oracle, "TriggerThresholdSet").withArgs(assetA, DEFAULT_THRESHOLD, newTrigger); + await expect(tx).to.emit(oracle, "ResetThresholdSet").withArgs(assetA, DEFAULT_RESET_THRESHOLD, newReset); + + const state = await oracle.assetProtectionConfig(assetA); + expect(state.triggerThreshold).to.equal(newTrigger); + expect(state.resetThreshold).to.equal(newReset); + }); + + it("only emits TriggerThresholdSet when only trigger changes", async () => { + const newTrigger = parseUnits("0.25", 18); + const tx = await oracle.setThresholds(assetA, newTrigger, DEFAULT_RESET_THRESHOLD); + await expect(tx).to.emit(oracle, "TriggerThresholdSet").withArgs(assetA, DEFAULT_THRESHOLD, newTrigger); + await expect(tx).to.not.emit(oracle, "ResetThresholdSet"); + }); + + it("only emits ResetThresholdSet when only reset changes", async () => { + const newReset = parseUnits("0.08", 18); + const tx = await oracle.setThresholds(assetA, DEFAULT_THRESHOLD, newReset); + await expect(tx).to.not.emit(oracle, "TriggerThresholdSet"); + await expect(tx).to.emit(oracle, "ResetThresholdSet").withArgs(assetA, DEFAULT_RESET_THRESHOLD, newReset); + }); + + it("emits no events when neither changes", async () => { + const tx = await oracle.setThresholds(assetA, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD); + await expect(tx).to.not.emit(oracle, "TriggerThresholdSet"); + await expect(tx).to.not.emit(oracle, "ResetThresholdSet"); + }); + + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect( + oracle.setThresholds(assetA, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("reverts when asset is zero address", async () => { + await expect( + oracle.setThresholds(addr0000, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "ZeroAddressNotAllowed"); + }); + + it("reverts when not initialized", async () => { + await expect( + oracle.setThresholds(assetB, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "MarketNotInitialized"); + }); + + it("reverts when trigger below MIN_THRESHOLD", async () => { + await expect( + oracle.setThresholds(assetA, MIN_THRESHOLD.sub(1), DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "ThresholdBelowMinimum"); + }); + + it("reverts when trigger above MAX_THRESHOLD", async () => { + await expect( + oracle.setThresholds(assetA, MAX_THRESHOLD.add(1), DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "ThresholdAboveMaximum"); + }); + + it("reverts when reset >= trigger (InvalidResetThreshold)", async () => { + await expect(oracle.setThresholds(assetA, DEFAULT_THRESHOLD, DEFAULT_THRESHOLD)).to.be.revertedWithCustomError( + oracle, + "InvalidResetThreshold", + ); + }); + + it("reverts when triggerThreshold is zero", async () => { + await expect(oracle.setThresholds(assetA, 0, DEFAULT_RESET_THRESHOLD)).to.be.revertedWithCustomError( + oracle, + "ZeroValueNotAllowed", + ); + }); + + it("reverts when resetThreshold is zero", async () => { + await expect(oracle.setThresholds(assetA, DEFAULT_THRESHOLD, 0)).to.be.revertedWithCustomError( + oracle, + "ZeroValueNotAllowed", + ); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 6. setAssetBoundedPricingEnabled + // ──────────────────────────────────────────────────────────────────────── + + describe("setAssetBoundedPricingEnabled", () => { + beforeEach(async () => { + await initAsset(assetA); + }); + + it("disables bounded pricing and emits event", async () => { + const tx = await oracle.setAssetBoundedPricingEnabled(assetA, false); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, false); + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(false); + }); + + it("enables bounded pricing and emits event", async () => { + await oracle.setAssetBoundedPricingEnabled(assetA, false); + const tx = await oracle.setAssetBoundedPricingEnabled(assetA, true); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, true); + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(true); + }); + + it("setAssetBoundedPricingEnabled(true) on already-enabled is a no-op", async () => { + const tx = await oracle.setAssetBoundedPricingEnabled(assetA, true); + await expect(tx).to.not.emit(oracle, "BoundedPricingWhitelistUpdated"); + }); + + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect(oracle.setAssetBoundedPricingEnabled(assetA, false)).to.be.revertedWithCustomError( + oracle, + "Unauthorized", + ); + }); + + it("reverts when asset is zero address", async () => { + await expect(oracle.setAssetBoundedPricingEnabled(addr0000, true)).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("reverts when not initialized", async () => { + await expect(oracle.setAssetBoundedPricingEnabled(assetB, true)).to.be.revertedWithCustomError( + oracle, + "MarketNotInitialized", + ); + }); + + it("reverts when disabling with active protection", async () => { + await initAssetWithWindow(assetB); + + // Trigger protection via pump: spot > MIN_PRICE * 1.2 = 1.08 + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetB).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenB.address); + expect(await oracle.currentlyUsingProtectedPrice(assetB)).to.equal(true); + + await expect(oracle.setAssetBoundedPricingEnabled(assetB, false)).to.be.revertedWithCustomError( + oracle, + "ProtectedPriceActive", + ); + }); + + it("price functions return spot after disabling bounded pricing", async () => { + await initAssetWithWindow(assetB); + // Set a pump spot that would trigger bounded pricing + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetB).returns(pumpSpot); + + // View detects deviation → returns bounded (MIN_PRICE) + expect(await oracle.getBoundedCollateralPriceView(vTokenB.address)).to.equal(MIN_PRICE); + + // Disable bounded pricing (protection not yet stored active, so this succeeds) + await oracle.setAssetBoundedPricingEnabled(assetB, false); + + // Same pump spot, but now returns raw spot — not bounded + expect(await oracle.getBoundedCollateralPriceView(vTokenB.address)).to.equal(pumpSpot); + expect(await oracle.getBoundedDebtPriceView(vTokenB.address)).to.equal(pumpSpot); + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenB.address); + expect(collateral).to.equal(pumpSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 6b. setCachingEnabled + // ──────────────────────────────────────────────────────────────────────── + + describe("setCachingEnabled", () => { + beforeEach(async () => { + await initAsset(assetA); + }); + + it("disables caching and emits CachingEnabledUpdated", async () => { + const tx = await oracle.setCachingEnabled(assetA, false); + await expect(tx).to.emit(oracle, "CachingEnabledUpdated").withArgs(assetA, true, false); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.cachingEnabled).to.equal(false); + }); + + it("re-enables caching and emits CachingEnabledUpdated", async () => { + await oracle.setCachingEnabled(assetA, false); + const tx = await oracle.setCachingEnabled(assetA, true); + await expect(tx).to.emit(oracle, "CachingEnabledUpdated").withArgs(assetA, false, true); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.cachingEnabled).to.equal(true); + }); + + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect(oracle.setCachingEnabled(assetA, false)).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("reverts when asset has not been initialized", async () => { + await expect(oracle.setCachingEnabled(assetB, false)).to.be.revertedWithCustomError( + oracle, + "MarketNotInitialized", + ); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 7. updateMinPrice + // ──────────────────────────────────────────────────────────────────────── + + describe("updateMinPrice", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("updates minPrice and emits event", async () => { + const newMin = parseUnits("0.85", 18); + const tx = await oracle.updateMinPrice(assetA, newMin); + await expect(tx).to.emit(oracle, "MinPriceUpdated").withArgs(assetA, MIN_PRICE, newMin); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(newMin); + }); + + it("allows setting price within keeper deadband (not enforced on-chain)", async () => { + // newMin close to current MIN_PRICE (within 5% deadband) + const newMin = MIN_PRICE.sub(MIN_PRICE.mul(3).div(100)); // 3% below current min + await expect(oracle.updateMinPrice(assetA, newMin)).to.not.be.reverted; + }); + + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect(oracle.updateMinPrice(assetA, parseUnits("0.85", 18))).to.be.revertedWithCustomError( + oracle, + "Unauthorized", + ); + }); + + it("reverts when asset is zero address", async () => { + await expect(oracle.updateMinPrice(addr0000, parseUnits("0.85", 18))).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("reverts when price is zero", async () => { + await expect(oracle.updateMinPrice(assetA, 0)).to.be.revertedWithCustomError(oracle, "ZeroPriceNotAllowed"); + }); + + it("reverts when not initialized", async () => { + await expect(oracle.updateMinPrice(assetB, parseUnits("0.85", 18))).to.be.revertedWithCustomError( + oracle, + "MarketNotInitialized", + ); + }); + + it("reverts when newMin > currentSpot", async () => { + const aboveSpot = SPOT_PRICE.add(1); + await expect(oracle.updateMinPrice(assetA, aboveSpot)).to.be.revertedWithCustomError(oracle, "InvalidMinPrice"); + }); + + it("reverts when newMin > maxPrice", async () => { + // Set spot above maxPrice so the spot constraint passes; the maxPrice check is what reverts + resilientOracle.getPrice.whenCalledWith(assetA).returns(MAX_PRICE.add(parseUnits("0.05", 18))); + const aboveMax = MAX_PRICE.add(1); + await expect(oracle.updateMinPrice(assetA, aboveMax)).to.be.revertedWithCustomError(oracle, "InvalidMinPrice"); + }); + + it("succeeds when newMin == maxPrice == spot (full convergence)", async () => { + // Spot equal to maxPrice so newMin = maxPrice = spot is valid under the relaxed semantics + resilientOracle.getPrice.whenCalledWith(assetA).returns(MAX_PRICE); + await expect(oracle.updateMinPrice(assetA, MAX_PRICE)).to.not.be.reverted; + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(MAX_PRICE); + expect(state.maxPrice).to.equal(MAX_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 8. updateMaxPrice + // ──────────────────────────────────────────────────────────────────────── + + describe("updateMaxPrice", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("updates maxPrice and emits event", async () => { + const newMax = parseUnits("1.15", 18); + const tx = await oracle.updateMaxPrice(assetA, newMax); + await expect(tx).to.emit(oracle, "MaxPriceUpdated").withArgs(assetA, MAX_PRICE, newMax); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.maxPrice).to.equal(newMax); + }); + + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect(oracle.updateMaxPrice(assetA, parseUnits("1.15", 18))).to.be.revertedWithCustomError( + oracle, + "Unauthorized", + ); + }); + + it("reverts when asset is zero address", async () => { + await expect(oracle.updateMaxPrice(addr0000, parseUnits("1.15", 18))).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("reverts when price is zero", async () => { + await expect(oracle.updateMaxPrice(assetA, 0)).to.be.revertedWithCustomError(oracle, "ZeroPriceNotAllowed"); + }); + + it("reverts when not initialized", async () => { + await expect(oracle.updateMaxPrice(assetB, parseUnits("1.15", 18))).to.be.revertedWithCustomError( + oracle, + "MarketNotInitialized", + ); + }); + + it("reverts when newMax < currentSpot", async () => { + const belowSpot = SPOT_PRICE.sub(1); + await expect(oracle.updateMaxPrice(assetA, belowSpot)).to.be.revertedWithCustomError(oracle, "InvalidMaxPrice"); + }); + + it("reverts when newMax < minPrice", async () => { + // Set spot below minPrice so the spot constraint passes; the minPrice check is what reverts + resilientOracle.getPrice.whenCalledWith(assetA).returns(MIN_PRICE.sub(parseUnits("0.05", 18))); + const belowMin = MIN_PRICE.sub(1); + await expect(oracle.updateMaxPrice(assetA, belowMin)).to.be.revertedWithCustomError(oracle, "InvalidMaxPrice"); + }); + + it("succeeds when newMax == minPrice == spot (full convergence)", async () => { + // Spot equal to minPrice so newMax = minPrice = spot is valid under the relaxed semantics + resilientOracle.getPrice.whenCalledWith(assetA).returns(MIN_PRICE); + await expect(oracle.updateMaxPrice(assetA, MIN_PRICE)).to.not.be.reverted; + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(MIN_PRICE); + expect(state.maxPrice).to.equal(MIN_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 9. exitProtectionMode + // ──────────────────────────────────────────────────────────────────────── + + describe("exitProtectionMode", () => { + it("disables protection after governance raises reset threshold", async () => { + await initAssetWithWindow(assetA); + + // Trigger via pump: pumpSpot > MIN_PRICE * 1.2 = 1.08 + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Wait cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Post-trigger: min=0.9, max=pumpSpot (~1.08). Range exceeds default reset threshold (10%). + // Governance raises reset threshold above current range to allow disable. + const stateAfter = await oracle.assetProtectionConfig(assetA); + const range = stateAfter.maxPrice.sub(stateAfter.minPrice).mul(EXP_SCALE).div(stateAfter.minPrice); + const newResetThreshold = range.add(parseUnits("0.001", 18)); + + // resetThreshold must remain < triggerThreshold; raise triggerThreshold first if needed + const currentTrigger = stateAfter.triggerThreshold; + if (newResetThreshold.gte(currentTrigger)) { + await oracle.setThresholds(assetA, newResetThreshold.add(parseUnits("0.01", 18)), newResetThreshold); + } else { + await oracle.setThresholds(assetA, currentTrigger, newResetThreshold); + } + + const tx = await oracle.exitProtectionMode(assetA); + await expect(tx).to.emit(oracle, "ProtectionModeExited").withArgs(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("prices revert to spot after exitProtectionMode", async () => { + await initAssetWithWindow(assetA); + + // Trigger via pump + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // During protection: collateral = min(pumpSpot, MIN_PRICE) = MIN_PRICE (bounded) + const boundedCollateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(boundedCollateral).to.equal(MIN_PRICE); + + // Disable protection + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + const stateAfter = await oracle.assetProtectionConfig(assetA); + const range = stateAfter.maxPrice.sub(stateAfter.minPrice).mul(EXP_SCALE).div(stateAfter.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const currentTrigger = stateAfter.triggerThreshold; + if (newReset.gte(currentTrigger)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, currentTrigger, newReset); + } + await oracle.exitProtectionMode(assetA); + + // Verify lastProtectionTriggeredAt is reset to 0 on disable + const stateDisabled = await oracle.assetProtectionConfig(assetA); + expect(stateDisabled.lastProtectionTriggeredAt).to.equal(0); + expect(stateDisabled.currentlyUsingProtectedPrice).to.equal(false); + + // After disable: set spot back to normal + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + // Should return spot, not bounded price + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(collateral).to.equal(SPOT_PRICE); + expect(debt).to.equal(SPOT_PRICE); + }); + + it("reverts when caller is unauthorized", async () => { + await initAsset(assetA); + acm.isAllowedToCall.returns(false); + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("reverts when protection is not active", async () => { + await initAsset(assetA); + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "ProtectedPriceInactive"); + }); + + it("reverts when cooldown has not elapsed", async () => { + await initAssetWithWindow(assetA); + // Trigger protection + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + }); + + it("reverts when range not converged", async () => { + await initAssetWithWindow(assetA); + // Trigger protection + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + // Wait cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Range still wide -> reverts + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "PriceRangeNotConverged"); + }); + + it("reverts when rangeRatio exactly at resetThreshold (uses >=)", async () => { + await initAssetWithWindow(assetA); + + // Trigger + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Compute current range ratio and set resetThreshold exactly equal to it + const stateAfter = await oracle.assetProtectionConfig(assetA); + const rangeRatio = stateAfter.maxPrice.sub(stateAfter.minPrice).mul(EXP_SCALE).div(stateAfter.minPrice); + + // Raise triggerThreshold if needed so we can set resetThreshold = rangeRatio + const currentTrigger = stateAfter.triggerThreshold; + if (rangeRatio.gte(currentTrigger)) { + await oracle.setThresholds(assetA, rangeRatio.add(parseUnits("0.01", 18)), rangeRatio); + } else { + await oracle.setThresholds(assetA, currentTrigger, rangeRatio); + } + + // Set resetThreshold = rangeRatio (exactly equal, should revert since >=) + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "PriceRangeNotConverged"); + }); + + it("succeeds when rangeRatio at resetThreshold - 1 wei", async () => { + await initAssetWithWindow(assetA); + + // Trigger + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Set resetThreshold = rangeRatio + 1 (should succeed) + const stateAfter = await oracle.assetProtectionConfig(assetA); + const rangeRatio = stateAfter.maxPrice.sub(stateAfter.minPrice).mul(EXP_SCALE).div(stateAfter.minPrice); + + // Raise triggerThreshold if needed + const currentTrigger = stateAfter.triggerThreshold; + if (rangeRatio.add(1).gte(currentTrigger)) { + await oracle.setThresholds(assetA, rangeRatio.add(parseUnits("0.01", 18)), rangeRatio.add(1)); + } else { + await oracle.setThresholds(assetA, currentTrigger, rangeRatio.add(1)); + } + + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 9b. syncPriceBoundsAndProtections (keeper batch) + // ──────────────────────────────────────────────────────────────────────── + + describe("syncPriceBoundsAndProtections", () => { + // KeeperAction enum: 0 = SetMinPrice, 1 = SetMaxPrice, 2 = ExitProtectionMode + const SetMinPrice = 0; + const SetMaxPrice = 1; + const ExitProtectionMode = 2; + + beforeEach(async () => { + await initAssetWithWindow(assetA); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("reverts when caller is unauthorized", async () => { + acm.isAllowedToCall.returns(false); + await expect( + oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: SetMinPrice, value: parseUnits("0.85", 18) }]), + ).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("succeeds with empty array (no-op)", async () => { + await expect(oracle.syncPriceBoundsAndProtections([])).to.not.be.reverted; + }); + + it("single SetMinPrice item updates the asset and emits MinPriceUpdated", async () => { + const newMin = parseUnits("0.85", 18); + const tx = await oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: SetMinPrice, value: newMin }]); + await expect(tx).to.emit(oracle, "MinPriceUpdated").withArgs(assetA, MIN_PRICE, newMin); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(newMin); + }); + + it("single SetMaxPrice item updates the asset and emits MaxPriceUpdated", async () => { + const newMax = parseUnits("1.15", 18); + const tx = await oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: SetMaxPrice, value: newMax }]); + await expect(tx).to.emit(oracle, "MaxPriceUpdated").withArgs(assetA, MAX_PRICE, newMax); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.maxPrice).to.equal(newMax); + }); + + it("single ExitProtectionMode item clears protection and emits ProtectionModeExited", async () => { + // Pre-arm: pump trigger so protection is active + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Cooldown elapses; raise reset threshold above the range so exit gate passes + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + const stateAfter = await oracle.assetProtectionConfig(assetA); + const range = stateAfter.maxPrice.sub(stateAfter.minPrice).mul(EXP_SCALE).div(stateAfter.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const currentTrigger = stateAfter.triggerThreshold; + if (newReset.gte(currentTrigger)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, currentTrigger, newReset); + } + + const tx = await oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: ExitProtectionMode, value: 0 }]); + await expect(tx).to.emit(oracle, "ProtectionModeExited").withArgs(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("mixed batch (SetMin, SetMax, Exit) converges and exits in one tx", async () => { + // Pre-arm: pump trigger + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Spot stabilises somewhere inside the post-trigger window + const stableSpot = MIN_PRICE.add(pumpSpot).div(2); + resilientOracle.getPrice.whenCalledWith(assetA).returns(stableSpot); + + // Wait out cooldown so the Exit action is admissible + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + const tx = await oracle.syncPriceBoundsAndProtections([ + { asset: assetA, action: SetMinPrice, value: stableSpot }, + { asset: assetA, action: SetMaxPrice, value: stableSpot }, + { asset: assetA, action: ExitProtectionMode, value: 0 }, + ]); + + await expect(tx) + .to.emit(oracle, "MinPriceUpdated") + .and.to.emit(oracle, "MaxPriceUpdated") + .and.to.emit(oracle, "ProtectionModeExited") + .withArgs(assetA); + + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(stableSpot); + expect(state.maxPrice).to.equal(stableSpot); + expect(state.currentlyUsingProtectedPrice).to.equal(false); + }); + + it("revert in any item rolls back the whole batch", async () => { + // Item 1 is a valid SetMinPrice; item 2 is SetMinPrice with value above current spot — must revert. + // After revert, the asset's minPrice must remain at its pre-batch value (no partial application). + const validNewMin = parseUnits("0.85", 18); + const stateBefore = await oracle.assetProtectionConfig(assetA); + const aboveSpot = SPOT_PRICE.add(parseUnits("0.5", 18)); + + await expect( + oracle.syncPriceBoundsAndProtections([ + { asset: assetA, action: SetMinPrice, value: validNewMin }, + { asset: assetA, action: SetMinPrice, value: aboveSpot }, + ]), + ).to.be.revertedWithCustomError(oracle, "InvalidMinPrice"); + + const stateAfter = await oracle.assetProtectionConfig(assetA); + expect(stateAfter.minPrice).to.equal(stateBefore.minPrice); + }); + + it("reverts with PriceExceedsUint128 when a SetMin/SetMax value overflows uint128", async () => { + const overflow = BigNumber.from(2).pow(128); + await expect( + oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: SetMinPrice, value: overflow }]), + ).to.be.revertedWithCustomError(oracle, "PriceExceedsUint128"); + }); + + it("re-uses per-action validation: SetMinPrice with value > spot still reverts with InvalidMinPrice", async () => { + const aboveSpot = SPOT_PRICE.add(1); + await expect( + oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: SetMinPrice, value: aboveSpot }]), + ).to.be.revertedWithCustomError(oracle, "InvalidMinPrice"); + }); + + it("re-uses per-action validation: ExitProtectionMode before cooldown still reverts with CooldownNotElapsed", async () => { + // Pre-arm protection without waiting cooldown + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + await expect( + oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: ExitProtectionMode, value: 0 }]), + ).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + }); + + // Note: the catch-all `revert InvalidKeeperAction(...)` branch is defensive for future enum + // additions. With the current 3-value enum, Solidity's abi-boundary enum range check rejects + // out-of-range action values before the function body executes, so the branch is unreachable + // through a well-formed external call and is left untested. + }); + + // ──────────────────────────────────────────────────────────────────────── + // 10. getBoundedCollateralPrice + // ──────────────────────────────────────────────────────────────────────── + + describe("getBoundedCollateralPrice", () => { + beforeEach(async () => { + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("returns spot when not whitelisted", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + const price = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(price).to.equal(SPOT_PRICE); + // Verify oracle was called + expect(resilientOracle.getPrice).to.have.been.calledWith(assetA); + }); + + it("returns spot when whitelisted, no deviation", async () => { + await initAssetWithWindow(assetA); + const price = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(price).to.equal(SPOT_PRICE); + }); + + it("expands window when spot < minPrice", async () => { + await initAssetWithWindow(assetA); + const lowSpot = MIN_PRICE.sub(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(lowSpot); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "MinPriceUpdated").withArgs(assetA, MIN_PRICE, lowSpot); + }); + + it("expands window when spot > maxPrice", async () => { + await initAssetWithWindow(assetA); + const highSpot = MAX_PRICE.add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(highSpot); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "MaxPriceUpdated").withArgs(assetA, MAX_PRICE, highSpot); + }); + + it("triggers protection on pump and returns minPrice (exact arithmetic)", async () => { + await initAssetWithWindow(assetA); + // upperBound = MIN_PRICE * (1 + threshold) = 0.9 * 1.2 = 1.08 + const upperBound = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE); + const pumpSpot = upperBound.add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + + // Verify state + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Read bounded price after trigger (protection already active, no re-trigger) + // collateral = min(pumpSpot, MIN_PRICE) = MIN_PRICE + const boundedPrice = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(boundedPrice).to.equal(MIN_PRICE); + }); + + it("triggers protection on crash and returns spot (exact arithmetic)", async () => { + // Use a tight window so crashSpot falls below localMin, causing window expansion + const localMin = parseUnits("0.995", 18); + const localMax = parseUnits("1.005", 18); + await initAssetWithWindow(assetA, localMin, localMax); + + // lowerBound = localMax * (1 - threshold) = 1.005 * 0.8 = 0.804 + const lowerBound = localMax.mul(EXP_SCALE.sub(DEFAULT_THRESHOLD)).div(EXP_SCALE); + const crashSpot = lowerBound.sub(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(crashSpot); + + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + + // After expansion, min = crashSpot (since crashSpot < localMin). + // collateral = min(crashSpot, crashSpot) = crashSpot + const boundedPrice = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(boundedPrice).to.equal(crashSpot); + }); + + it("expands window min and max while protection is already active", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection via pump + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Spot drops below min — window expands downward, deviation still exceeded so event re-emitted + const lowSpot = parseUnits("0.8", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(lowSpot); + const tx1 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx1).to.emit(oracle, "MinPriceUpdated").withArgs(assetA, MIN_PRICE, lowSpot); + await expect(tx1).to.emit(oracle, "ProtectionTriggered"); + + // Spot rises above current max — window expands upward, deviation still exceeded + const stateAfterMin = await oracle.assetProtectionConfig(assetA); + const highSpot = stateAfterMin.maxPrice.add(parseUnits("0.5", 18)); + resilientOracle.getPrice.whenCalledWith(assetA).returns(highSpot); + const tx2 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx2).to.emit(oracle, "MaxPriceUpdated"); + await expect(tx2).to.emit(oracle, "ProtectionTriggered"); + }); + + it("returns spot for uninitialized asset (setTokenConfig never called)", async () => { + // assetA is NOT initialized — isBoundedPricingEnabled defaults to false + const price = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(price).to.equal(SPOT_PRICE); + + const debtPrice = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(debtPrice).to.equal(SPOT_PRICE); + + // Views also pass through to spot + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + expect(await oracle.getBoundedDebtPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("reverts when vToken is zero address", async () => { + await initAsset(assetA); + await expect(oracle.getBoundedCollateralPrice(addr0000)).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("resolves native market to NATIVE_TOKEN_ADDR", async () => { + const nativeAsset = NATIVE_TOKEN_ADDR; + resilientOracle.getPrice.whenCalledWith(nativeAsset).returns(SPOT_PRICE); + await initAssetWithWindow(nativeAsset); + const price = await oracle.callStatic.getBoundedCollateralPrice(nativeMarket.address); + expect(price).to.equal(SPOT_PRICE); + }); + + it("reverts with PriceExceedsUint128 when oracle returns > uint128 max", async () => { + await initAsset(assetA); + const overflowPrice = BigNumber.from(2).pow(128); + resilientOracle.getPrice.whenCalledWith(assetA).returns(overflowPrice); + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)).to.be.revertedWithCustomError( + oracle, + "PriceExceedsUint128", + ); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 11. getBoundedDebtPrice + // ──────────────────────────────────────────────────────────────────────── + + describe("getBoundedDebtPrice", () => { + beforeEach(async () => { + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("returns spot when not whitelisted", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + const price = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(price).to.equal(SPOT_PRICE); + }); + + it("returns spot when no deviation", async () => { + await initAssetWithWindow(assetA); + const price = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(price).to.equal(SPOT_PRICE); + }); + + it("returns spot on pump trigger (spot > max after expansion)", async () => { + // Use a tight window so pumpSpot exceeds localMax, causing window expansion + const localMin = parseUnits("0.995", 18); + const localMax = parseUnits("1.005", 18); + await initAssetWithWindow(assetA, localMin, localMax); + + // upperBound = 0.995 * 1.2 = 1.194 + const upperBound = localMin.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE); + const pumpSpot = upperBound.add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + + await oracle.getBoundedDebtPrice(vTokenA.address); + // After expansion max = pumpSpot (since pumpSpot > localMax). + // debt = max(pumpSpot, pumpSpot) = pumpSpot + const price = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(price).to.equal(pumpSpot); + }); + + it("returns maxPrice on crash trigger (spot < max)", async () => { + await initAssetWithWindow(assetA); + // lowerBound = MAX_PRICE * (1 - threshold) = 1.1 * 0.8 = 0.88 + const lowerBound = MAX_PRICE.mul(EXP_SCALE.sub(DEFAULT_THRESHOLD)).div(EXP_SCALE); + const crashSpot = lowerBound.sub(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(crashSpot); + + await oracle.getBoundedDebtPrice(vTokenA.address); + // After expansion, min = crashSpot. debt = max(spot, max). + // crashSpot < MAX_PRICE, so max(crashSpot, MAX_PRICE) = MAX_PRICE + const price = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(price).to.equal(MAX_PRICE); + }); + + it("reverts when vToken is zero address", async () => { + await initAsset(assetA); + await expect(oracle.getBoundedDebtPrice(addr0000)).to.be.revertedWithCustomError(oracle, "ZeroAddressNotAllowed"); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 12. getBoundedPrices + // ──────────────────────────────────────────────────────────────────────── + + describe("getBoundedPrices", () => { + beforeEach(async () => { + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("returns (spot, spot) when not whitelisted", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + const [collateral, debt] = await oracle.callStatic.getBoundedPrices(vTokenA.address); + expect(collateral).to.equal(SPOT_PRICE); + expect(debt).to.equal(SPOT_PRICE); + }); + + it("returns (spot, spot) when whitelisted, no deviation", async () => { + await initAssetWithWindow(assetA); + const [collateral, debt] = await oracle.callStatic.getBoundedPrices(vTokenA.address); + expect(collateral).to.equal(SPOT_PRICE); + expect(debt).to.equal(SPOT_PRICE); + }); + + it("returns (minPrice, maxPrice) when protection active (pump scenario)", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection via pump + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // collateral = min(pumpSpot, MIN_PRICE) = MIN_PRICE + // After expansion max = pumpSpot (since pumpSpot > MAX_PRICE), debt = max(pumpSpot, pumpSpot) = pumpSpot + const stateAfter = await oracle.assetProtectionConfig(assetA); + const [collateral, debt] = await oracle.callStatic.getBoundedPrices(vTokenA.address); + expect(collateral).to.equal(MIN_PRICE); + expect(debt).to.equal(stateAfter.maxPrice); + }); + + it("both values match individual getBoundedCollateralPrice and getBoundedDebtPrice", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection via pump + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const collateralPrice = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debtPrice = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + const [collateral, debt] = await oracle.callStatic.getBoundedPrices(vTokenA.address); + + expect(collateral).to.equal(collateralPrice); + expect(debt).to.equal(debtPrice); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 13. View price functions (getBoundedCollateralPriceView, getBoundedDebtPriceView) + // ──────────────────────────────────────────────────────────────────────── + + describe("view price functions", () => { + beforeEach(async () => { + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("returns spot when not whitelisted", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + expect(await oracle.getBoundedDebtPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("returns spot when no protection and no deviation", async () => { + await initAssetWithWindow(assetA); + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + expect(await oracle.getBoundedDebtPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("returns bounded price when protection is stored active", async () => { + await initAssetWithWindow(assetA); + // Trigger protection first: pumpSpot > MIN_PRICE * 1.2 = 1.08 + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // View should return bounded prices + // collateral = min(pumpSpot, MIN_PRICE) = MIN_PRICE + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(MIN_PRICE); + // debt = max(pumpSpot, max). After expansion max = pumpSpot, so pumpSpot. + const stateAfter = await oracle.assetProtectionConfig(assetA); + expect(await oracle.getBoundedDebtPriceView(vTokenA.address)).to.equal(stateAfter.maxPrice); + }); + + it("simulated trigger: returns bounded price without state mutation", async () => { + await initAssetWithWindow(assetA); + // Spot exceeds deviation threshold but protection has NOT been triggered yet + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + + // View should detect deviation and return bounded price + // collateral = min(pumpSpot, MIN_PRICE) = MIN_PRICE + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(MIN_PRICE); + + // Verify currentlyUsingProtectedPrice is still false (no state mutation) + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("view and non-view return identical prices when protection is active (pump)", async () => { + await initAssetWithWindow(assetA); + // Trigger protection via pump + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Read from non-view (callStatic) and view + const nonViewCollateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const nonViewDebt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + const viewCollateral = await oracle.getBoundedCollateralPriceView(vTokenA.address); + const viewDebt = await oracle.getBoundedDebtPriceView(vTokenA.address); + + expect(nonViewCollateral).to.equal(viewCollateral); + expect(nonViewDebt).to.equal(viewDebt); + }); + + it("view and non-view return identical prices when protection is active (crash)", async () => { + const localMin = parseUnits("0.995", 18); + const localMax = parseUnits("1.005", 18); + await initAssetWithWindow(assetA, localMin, localMax); + + // Trigger protection via crash + const lowerBound = localMax.mul(EXP_SCALE.sub(DEFAULT_THRESHOLD)).div(EXP_SCALE); + const crashSpot = lowerBound.sub(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(crashSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + const nonViewCollateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const nonViewDebt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + const viewCollateral = await oracle.getBoundedCollateralPriceView(vTokenA.address); + const viewDebt = await oracle.getBoundedDebtPriceView(vTokenA.address); + + expect(nonViewCollateral).to.equal(viewCollateral); + expect(nonViewDebt).to.equal(viewDebt); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 14. getBoundedPricesView + // ──────────────────────────────────────────────────────────────────────── + + describe("getBoundedPricesView", () => { + beforeEach(async () => { + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("returns same as individual view functions", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const collateralView = await oracle.getBoundedCollateralPriceView(vTokenA.address); + const debtView = await oracle.getBoundedDebtPriceView(vTokenA.address); + const [collateral, debt] = await oracle.getBoundedPricesView(vTokenA.address); + + expect(collateral).to.equal(collateralView); + expect(debt).to.equal(debtView); + }); + + it("works with cache (after updateProtectionState)", async () => { + await initAssetWithWindow(assetA); + + // Deploy the caller helper to test transient cache within the same tx + const callerFactory = await ethers.getContractFactory("DeviationBoundedOracleCaller", admin); + const caller = await callerFactory.deploy(oracle.address); + + // Trigger protection first so bounded prices differ from spot + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + // Use caller to updateProtectionState then read view within same tx + const [collateral, debt] = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + + // Should match view prices + const collateralView = await oracle.getBoundedCollateralPriceView(vTokenA.address); + const debtView = await oracle.getBoundedDebtPriceView(vTokenA.address); + expect(collateral).to.equal(collateralView); + expect(debt).to.equal(debtView); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 15. updateProtectionState + // ──────────────────────────────────────────────────────────────────────── + + describe("updateProtectionState", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("expands window and triggers protection for whitelisted asset", async () => { + // pumpSpot > MIN_PRICE * 1.2 = 1.08 + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + + const tx = await oracle.updateProtectionState(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + + it("is a no-op for non-whitelisted asset", async () => { + await oracle.setAssetBoundedPricingEnabled(assetA, false); + // Should not revert + await expect(oracle.updateProtectionState(vTokenA.address)).to.not.be.reverted; + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("reverts with PriceExceedsUint128 when oracle returns > uint128 max", async () => { + const overflowPrice = BigNumber.from(2).pow(128); + resilientOracle.getPrice.whenCalledWith(assetA).returns(overflowPrice); + await expect(oracle.updateProtectionState(vTokenA.address)).to.be.revertedWithCustomError( + oracle, + "PriceExceedsUint128", + ); + }); + + it("expands window without triggering protection", async () => { + // Use 30% threshold so spot=1.15 expands max but stays under upperBound (0.9 * 1.3 = 1.17) + await initAssetWithWindow( + assetB, + MIN_PRICE, + MAX_PRICE, + DEFAULT_COOLDOWN, + parseUnits("0.3", 18), + DEFAULT_RESET_THRESHOLD, + ); + + const spot = parseUnits("1.15", 18); + resilientOracle.getPrice.whenCalledWith(assetB).returns(spot); + + const tx = await oracle.updateProtectionState(vTokenB.address); + await expect(tx).to.emit(oracle, "MaxPriceUpdated").withArgs(assetB, MAX_PRICE, spot); + await expect(tx).to.not.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetB)).to.equal(false); + }); + + it("triggers protection without expanding window", async () => { + // Wide window [0.8, 1.3] — spot=1.0 is inside, no expansion needed + // But upperBound = 0.8 * 1.2 = 0.96 → spot 1.0 > 0.96 → triggers + await initAssetWithWindow(assetB, parseUnits("0.8", 18), parseUnits("1.3", 18)); + + resilientOracle.getPrice.whenCalledWith(assetB).returns(SPOT_PRICE); + + const tx = await oracle.updateProtectionState(vTokenB.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + await expect(tx).to.not.emit(oracle, "MinPriceUpdated"); + await expect(tx).to.not.emit(oracle, "MaxPriceUpdated"); + expect(await oracle.currentlyUsingProtectedPrice(assetB)).to.equal(true); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 15b. transient cache gating (DBO.cachingEnabled) + // ──────────────────────────────────────────────────────────────────────── + + describe("transient cache gating (DBO.cachingEnabled)", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let caller: any; + + beforeEach(async () => { + await initAssetWithWindow(assetA); + const callerFactory = await ethers.getContractFactory("DeviationBoundedOracleCaller", admin); + caller = await callerFactory.deploy(oracle.address); + resilientOracle.getPrice.reset(); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("view reads hit the cache when cachingEnabled is true (default)", async () => { + await caller.updateAndGetBothPrices(vTokenA.address); + + // updateProtectionState fetches the spot once; both view getters read from the + // transient cache and do not re-query the ResilientOracle. + expect(resilientOracle.getPrice).to.have.callCount(1); + }); + + it("view reads bypass the cache when cachingEnabled is false", async () => { + await oracle.setCachingEnabled(assetA, false); + resilientOracle.getPrice.reset(); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + await caller.updateAndGetBothPrices(vTokenA.address); + + // updateProtectionState fetches once; each view getter recomputes and fetches again. + expect(resilientOracle.getPrice).to.have.callCount(3); + }); + + it("disabling caching mid-window does not serve stale prices from prior cache writes", async () => { + // Seed the cache first by running update with the original spot. + await caller.updateAndGetBothPrices(vTokenA.address); + + // Disable caching and change the spot — views must recompute with the new spot. + await oracle.setCachingEnabled(assetA, false); + const newSpot = parseUnits("1.05", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(newSpot); + + const [collateral, debt] = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(collateral).to.equal(newSpot); + expect(debt).to.equal(newSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 16. isBoundedPricingEnabled + // ──────────────────────────────────────────────────────────────────────── + + describe("isBoundedPricingEnabled", () => { + it("returns true for enabled asset", async () => { + await initAsset(assetA); + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(true); + }); + + it("returns false for disabled asset", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(false); + }); + + it("returns false for uninitialized asset", async () => { + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(false); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 17. currentlyUsingProtectedPrice + // ──────────────────────────────────────────────────────────────────────── + + describe("currentlyUsingProtectedPrice", () => { + it("returns true when protection is active", async () => { + await initAssetWithWindow(assetA); + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + + it("returns false when protection is not active", async () => { + await initAsset(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("returns false for uninitialized asset", async () => { + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 18. canExitProtection + // ──────────────────────────────────────────────────────────────────────── + + describe("canExitProtection", () => { + it("returns false when protection is not active", async () => { + await initAsset(assetA); + expect(await oracle.canExitProtection(assetA)).to.equal(false); + }); + + it("returns false for uninitialized asset", async () => { + expect(await oracle.canExitProtection(assetA)).to.equal(false); + }); + + it("returns false before cooldown elapsed", async () => { + await initAssetWithWindow(assetA); + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.canExitProtection(assetA)).to.equal(false); + }); + + it("returns false when range not converged (cooldown elapsed but range wide)", async () => { + await initAssetWithWindow(assetA); + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Range is wide after pump expansion + expect(await oracle.canExitProtection(assetA)).to.equal(false); + }); + + it("returns true when cooldown elapsed and range converged", async () => { + await initAssetWithWindow(assetA); + + // Trigger + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Raise reset threshold so range < resetThreshold + const stateAfter = await oracle.assetProtectionConfig(assetA); + const rangeRatio = stateAfter.maxPrice.sub(stateAfter.minPrice).mul(EXP_SCALE).div(stateAfter.minPrice); + const newReset = rangeRatio.add(parseUnits("0.001", 18)); + + // Raise triggerThreshold if needed so we can set resetThreshold + const currentTrigger = stateAfter.triggerThreshold; + if (newReset.gte(currentTrigger)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, currentTrigger, newReset); + } + expect(await oracle.canExitProtection(assetA)).to.equal(true); + }); + + it("returns false when rangeRatio exactly at resetThreshold (boundary precision)", async () => { + await initAssetWithWindow(assetA); + + // Trigger + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Set reset threshold exactly at range ratio + const stateAfter = await oracle.assetProtectionConfig(assetA); + const rangeRatio = stateAfter.maxPrice.sub(stateAfter.minPrice).mul(EXP_SCALE).div(stateAfter.minPrice); + + // Raise triggerThreshold if needed + const currentTrigger = stateAfter.triggerThreshold; + if (rangeRatio.gte(currentTrigger)) { + await oracle.setThresholds(assetA, rangeRatio.add(parseUnits("0.01", 18)), rangeRatio); + } else { + await oracle.setThresholds(assetA, currentTrigger, rangeRatio); + } + // Uses < so exactly equal -> false + expect(await oracle.canExitProtection(assetA)).to.equal(false); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 19. getInitializedAssets + // ──────────────────────────────────────────────────────────────────────── + + describe("getInitializedAssets", () => { + it("returns all initialized assets", async () => { + await initAsset(assetA); + await initAsset(assetB); + const initialized = await oracle.getInitializedAssets(); + expect(initialized).to.include(assetA); + expect(initialized).to.include(assetB); + expect(initialized.length).to.equal(2); + }); + + it("includes de-whitelisted assets", async () => { + await initAsset(assetA); + await initAsset(assetB); + await oracle.setAssetBoundedPricingEnabled(assetB, false); + + const initialized = await oracle.getInitializedAssets(); + expect(initialized.length).to.equal(2); + expect(initialized).to.include(assetA); + expect(initialized).to.include(assetB); + }); + + it("returns empty when none", async () => { + const initialized = await oracle.getInitializedAssets(); + expect(initialized.length).to.equal(0); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 20. getAllBoundedPricingEnabledAssets + // ──────────────────────────────────────────────────────────────────────── + + describe("getAllBoundedPricingEnabledAssets", () => { + it("returns correct filtered array", async () => { + await initAsset(assetA); + await initAsset(assetB); + const whitelisted = await oracle.getAllBoundedPricingEnabledAssets(); + expect(whitelisted).to.include(assetA); + expect(whitelisted).to.include(assetB); + expect(whitelisted.length).to.equal(2); + }); + + it("returns empty when none whitelisted", async () => { + const whitelisted = await oracle.getAllBoundedPricingEnabledAssets(); + expect(whitelisted.length).to.equal(0); + }); + + it("filters correctly after partial de-whitelist", async () => { + // Init 3 assets, de-whitelist middle one + const vTokenC = await makeVToken( + { name: "vTokenC", symbol: "vTKC" }, + { name: "TokenC", symbol: "TKC", decimals: 18 }, + ); + const assetC = await vTokenC.underlying(); + + await initAsset(assetA); + await initAsset(assetB); + await initAsset(assetC); + + await oracle.setAssetBoundedPricingEnabled(assetB, false); + + const whitelisted = await oracle.getAllBoundedPricingEnabledAssets(); + expect(whitelisted.length).to.equal(2); + expect(whitelisted[0]).to.equal(assetA); + expect(whitelisted[1]).to.equal(assetC); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 21. checkAndGetWindowDrift + // ──────────────────────────────────────────────────────────────────────── + + describe("checkAndGetWindowDrift", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + }); + + it("returns true when drift > KEEPER_DEADBAND", async () => { + // Proposed min 11% below current -> drift > 5% + const proposedMin = MIN_PRICE.mul(89).div(100); + const proposedMax = MAX_PRICE.mul(111).div(100); + const [needsMinUpdate, needsMaxUpdate] = await oracle.checkAndGetWindowDrift( + [assetA], + [proposedMin], + [proposedMax], + ); + expect(needsMinUpdate[0]).to.equal(true); + expect(needsMaxUpdate[0]).to.equal(true); + }); + + it("returns false when drift <= KEEPER_DEADBAND", async () => { + // Proposed within 3% of current + const proposedMin = MIN_PRICE.mul(97).div(100); + const proposedMax = MAX_PRICE.mul(103).div(100); + const [needsMinUpdate, needsMaxUpdate] = await oracle.checkAndGetWindowDrift( + [assetA], + [proposedMin], + [proposedMax], + ); + expect(needsMinUpdate[0]).to.equal(false); + expect(needsMaxUpdate[0]).to.equal(false); + }); + + it("returns false when both prices equal (drift = 0)", async () => { + const [needsMinUpdate, needsMaxUpdate] = await oracle.checkAndGetWindowDrift([assetA], [MIN_PRICE], [MAX_PRICE]); + expect(needsMinUpdate[0]).to.equal(false); + expect(needsMaxUpdate[0]).to.equal(false); + }); + + it("returns false when current or proposed price is zero", async () => { + // Uninitialized asset has minPrice=0, maxPrice=0 + const [needsMinUpdate, needsMaxUpdate] = await oracle.checkAndGetWindowDrift( + [assetB], + [parseUnits("1", 18)], + [parseUnits("1.2", 18)], + ); + expect(needsMinUpdate[0]).to.equal(false); + expect(needsMaxUpdate[0]).to.equal(false); + }); + + it("returns false when drift exactly at KEEPER_DEADBAND (strict >)", async () => { + // drift = |current - proposed| * 1e18 / current = KEEPER_DEADBAND exactly + // proposed = MIN_PRICE * (1 - 0.05) = 0.9 * 0.95 = 0.855 + const proposedMin = MIN_PRICE.mul(EXP_SCALE.sub(KEEPER_DEADBAND)).div(EXP_SCALE); + const [needsMinUpdate] = await oracle.checkAndGetWindowDrift([assetA], [proposedMin], [MAX_PRICE]); + expect(needsMinUpdate[0]).to.equal(false); + }); + + it("returns true when drift at KEEPER_DEADBAND + 1 wei", async () => { + const proposedMin = MIN_PRICE.mul(EXP_SCALE.sub(KEEPER_DEADBAND)).div(EXP_SCALE).sub(1); + const [needsMinUpdate] = await oracle.checkAndGetWindowDrift([assetA], [proposedMin], [MAX_PRICE]); + expect(needsMinUpdate[0]).to.equal(true); + }); + + it("covers both drift directions: proposed above and below on-chain", async () => { + // Proposed ABOVE on-chain (proposedPrice > currentPrice) + const proposedMinAbove = MIN_PRICE.mul(106).div(100); + const [needsMinAbove] = await oracle.checkAndGetWindowDrift([assetA], [proposedMinAbove], [MAX_PRICE]); + expect(needsMinAbove[0]).to.equal(true); + + // Proposed BELOW on-chain (proposedPrice < currentPrice) + const proposedMinBelow = MIN_PRICE.mul(94).div(100); + const [needsMinBelow] = await oracle.checkAndGetWindowDrift([assetA], [proposedMinBelow], [MAX_PRICE]); + expect(needsMinBelow[0]).to.equal(true); + }); + + it("returns [false, false] for uninitialized asset with non-zero proposed values", async () => { + const uninitAsset = someone.address; // random address, not initialized + const [needsMinUpdate, needsMaxUpdate] = await oracle.checkAndGetWindowDrift( + [uninitAsset], + [parseUnits("1", 18)], + [parseUnits("1.2", 18)], + ); + expect(needsMinUpdate[0]).to.equal(false); + expect(needsMaxUpdate[0]).to.equal(false); + }); + + it("reverts when array lengths mismatch", async () => { + await expect( + oracle.checkAndGetWindowDrift([assetA], [MIN_PRICE, MIN_PRICE], [MAX_PRICE]), + ).to.be.revertedWithCustomError(oracle, "InvalidArrayLength"); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 22. assetProtectionConfig getter + // ──────────────────────────────────────────────────────────────────────── + + describe("assetProtectionConfig getter", () => { + it("returns all struct fields correctly", async () => { + await initAsset(assetA); + + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(SPOT_PRICE); + expect(state.maxPrice).to.equal(SPOT_PRICE); + expect(state.currentlyUsingProtectedPrice).to.equal(false); + expect(state.isBoundedPricingEnabled).to.equal(true); + expect(state.lastProtectionTriggeredAt).to.equal(0); + expect(state.cooldownPeriod).to.equal(DEFAULT_COOLDOWN); + expect(state.asset).to.equal(assetA); + expect(state.triggerThreshold).to.equal(DEFAULT_THRESHOLD); + expect(state.resetThreshold).to.equal(DEFAULT_RESET_THRESHOLD); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // 23. Volatile price extends protection period + // ──────────────────────────────────────────────────────────────────────── + + describe("volatile price extends protection period", () => { + it("continued deviation updates lastProtectionTriggeredAt, extending cooldown", async () => { + await initAssetWithWindow(assetA); + + // 1. Trigger protection + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + const state1 = await oracle.assetProtectionConfig(assetA); + const firstTriggerTime = state1.lastProtectionTriggeredAt; + + // 2. Advance time by half cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // 3. Another deviating price → updates lastProtectionTriggeredAt + const biggerPump = pumpSpot.add(parseUnits("0.2", 18)); + resilientOracle.getPrice.whenCalledWith(assetA).returns(biggerPump); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + + const state2 = await oracle.assetProtectionConfig(assetA); + expect(state2.lastProtectionTriggeredAt).to.be.gt(firstTriggerTime); + + // 4. Advance time by half cooldown again (total = cooldown from initial trigger, + // but only half from the latest update) + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // 5. Try disable → should revert because cooldown restarted from the second trigger + const stateBeforeDisable = await oracle.assetProtectionConfig(assetA); + const range = stateBeforeDisable.maxPrice + .sub(stateBeforeDisable.minPrice) + .mul(EXP_SCALE) + .div(stateBeforeDisable.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const trigger = stateBeforeDisable.triggerThreshold; + if (newReset.gte(trigger)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, trigger, newReset); + } + + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + + // 6. Advance remaining half cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2 + 1]); + await ethers.provider.send("evm_mine", []); + + // 7. Now disable succeeds + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + }); + + it("protection period does NOT extend when price returns within threshold", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection + const pumpSpot2 = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot2); + await oracle.getBoundedCollateralPrice(vTokenA.address); + const state1 = await oracle.assetProtectionConfig(assetA); + const triggerTime = state1.lastProtectionTriggeredAt; + + // Set price back within threshold + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + + // Should NOT emit ProtectionTriggered, timestamp unchanged + await expect(tx).to.not.emit(oracle, "ProtectionTriggered"); + const state2 = await oracle.assetProtectionConfig(assetA); + expect(state2.lastProtectionTriggeredAt).to.equal(triggerTime); + + // Protection still active (time-gated) + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // M01: cooldown reset only on first trigger or genuine window expansion + // ──────────────────────────────────────────────────────────────────────── + + describe("Cooldown reset only on genuine window expansion", () => { + it("recovery after a crash does NOT reset the cooldown", async () => { + await initAssetWithWindow(assetA); + + // Crash: spot below maxPrice * (1 - threshold) = 1.1 * 0.8 = 0.88 + const crashSpot = parseUnits("0.6", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(crashSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + const stateAfterCrash = await oracle.assetProtectionConfig(assetA); + const t0 = stateAfterCrash.lastProtectionTriggeredAt; + expect(stateAfterCrash.minPrice).to.equal(crashSpot); + expect(stateAfterCrash.currentlyUsingProtectedPrice).to.equal(true); + + // Advance halfway through cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // Recovery: spot above minPrice * (1 + threshold) = 0.6 * 1.2 = 0.72, + // still within the existing window (no new low, no new high → windowExpanded = false). + // _exceedsDeviationThreshold returns true (recovery is misclassified as a pump), + // but the cooldown must NOT advance. + const recoverySpot = parseUnits("0.85", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(recoverySpot); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + + const stateAfterRecovery = await oracle.assetProtectionConfig(assetA); + expect(stateAfterRecovery.lastProtectionTriggeredAt).to.equal(t0); + expect(stateAfterRecovery.minPrice).to.equal(crashSpot); + expect(stateAfterRecovery.maxPrice).to.equal(MAX_PRICE); + }); + + it("a deeper crash (new low) DOES reset the cooldown", async () => { + await initAssetWithWindow(assetA); + + const firstCrash = parseUnits("0.6", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(firstCrash); + await oracle.getBoundedCollateralPrice(vTokenA.address); + const t0 = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // New low: minPrice expands → windowExpanded = true → cooldown resets + const deeperCrash = parseUnits("0.5", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(deeperCrash); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const stateAfter = await oracle.assetProtectionConfig(assetA); + expect(stateAfter.lastProtectionTriggeredAt).to.be.gt(t0); + expect(stateAfter.minPrice).to.equal(deeperCrash); + }); + + it("sustained pump within the existing window does NOT reset the cooldown", async () => { + await initAssetWithWindow(assetA); + + // Pump above maxPrice so the window expands on the first trigger + const firstPump = parseUnits("1.4", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(firstPump); + await oracle.getBoundedCollateralPrice(vTokenA.address); + const stateAfterFirstPump = await oracle.assetProtectionConfig(assetA); + const t0 = stateAfterFirstPump.lastProtectionTriggeredAt; + expect(stateAfterFirstPump.maxPrice).to.equal(firstPump); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // Still pump-classified (1.3 > 0.9 * 1.2 = 1.08) but no new high (1.3 < 1.4) + const sustainedPump = parseUnits("1.3", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(sustainedPump); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const stateAfter = await oracle.assetProtectionConfig(assetA); + expect(stateAfter.lastProtectionTriggeredAt).to.equal(t0); + expect(stateAfter.maxPrice).to.equal(firstPump); + }); + + it("exitProtectionMode becomes reachable when recovery does not refresh the cooldown", async () => { + await initAssetWithWindow(assetA); + + // Mild crash: spot < 1.1 * 0.8 = 0.88, with the resulting window narrow enough + // (~29%) to fit under MAX_THRESHOLD = 50% when we later bump setThresholds. + const crashSpot = parseUnits("0.85", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(crashSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + // Recovery within the existing window but above the post-crash pump threshold + // (1.05 > 0.85 * 1.2 = 1.02). _exceedsDeviationThreshold returns true, + // but no new low / no new high → windowExpanded = false → cooldown must NOT advance. + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + resilientOracle.getPrice.whenCalledWith(assetA).returns(parseUnits("1.05", 18)); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + // Finish the original cooldown window + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2 + 1]); + await ethers.provider.send("evm_mine", []); + + // Bump resetThreshold above the current range (~29.4%) so the convergence check passes + const stateBeforeExit = await oracle.assetProtectionConfig(assetA); + const range = stateBeforeExit.maxPrice.sub(stateBeforeExit.minPrice).mul(EXP_SCALE).div(stateBeforeExit.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const newTrigger = newReset.add(parseUnits("0.01", 18)); + await oracle.setThresholds(assetA, newTrigger, newReset); + + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + }); +}); diff --git a/test/DeviationBoundedOracleE2E.ts b/test/DeviationBoundedOracleE2E.ts new file mode 100644 index 00000000..3c713758 --- /dev/null +++ b/test/DeviationBoundedOracleE2E.ts @@ -0,0 +1,1192 @@ +import { FakeContract, smock } from "@defi-wonderland/smock"; +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; +import chai from "chai"; +import { BigNumber } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers, upgrades } from "hardhat"; + +import { + AccessControlManager, + DeviationBoundedOracle, + DeviationBoundedOracleCaller, + DeviationBoundedOracle__factory, + ResilientOracle, + VBEP20Harness, +} from "../typechain-types"; +import { makeVToken } from "./utils/makeVToken"; + +const { expect } = chai; +chai.use(smock.matchers); + +const EXP_SCALE = parseUnits("1", 18); +const DEFAULT_THRESHOLD = parseUnits("0.2", 18); // 20% +const DEFAULT_RESET_THRESHOLD = parseUnits("0.1", 18); // 10% +const DEFAULT_COOLDOWN = 3600; +const SPOT_PRICE = parseUnits("1", 18); +const MIN_PRICE = parseUnits("0.9", 18); +const MAX_PRICE = parseUnits("1.1", 18); +const KEEPER_DEADBAND = parseUnits("0.05", 18); + +describe("DeviationBoundedOracle E2E", () => { + let admin: SignerWithAddress; + let resilientOracle: FakeContract; + let acm: FakeContract; + let oracle: DeviationBoundedOracle; + let oracleFactory: DeviationBoundedOracle__factory; + let caller: DeviationBoundedOracleCaller; + let vTokenA: VBEP20Harness; + let vTokenB: VBEP20Harness; + let assetA: string; + let assetB: string; + let nativeMarket: VBEP20Harness; + let vaiToken: VBEP20Harness; + + before(async () => { + [admin] = await ethers.getSigners(); + oracleFactory = await ethers.getContractFactory("DeviationBoundedOracle", admin); + resilientOracle = await smock.fake("ResilientOracle"); + acm = await smock.fake("AccessControlManager"); + acm.isAllowedToCall.returns(true); + + vTokenA = await makeVToken( + { name: "vTokenA_E2E", symbol: "vTKA_E" }, + { name: "TokenA_E2E", symbol: "TKA_E", decimals: 18 }, + ); + vTokenB = await makeVToken( + { name: "vTokenB_E2E", symbol: "vTKB_E" }, + { name: "TokenB_E2E", symbol: "TKB_E", decimals: 18 }, + ); + nativeMarket = await makeVToken( + { name: "vNative_E2E", symbol: "vNAT_E" }, + { name: "Native_E2E", symbol: "NAT_E", decimals: 18 }, + ); + vaiToken = await makeVToken( + { name: "vVAI_E2E", symbol: "vVAI_E" }, + { name: "VAI_E2E", symbol: "VAI_E", decimals: 18 }, + ); + assetA = await vTokenA.underlying(); + assetB = await vTokenB.underlying(); + }); + + beforeEach(async () => { + acm.isAllowedToCall.returns(true); + resilientOracle.getPrice.reset(); + resilientOracle.getPrice.returns(SPOT_PRICE); + + oracle = await upgrades.deployProxy(oracleFactory, [acm.address], { + constructorArgs: [resilientOracle.address, nativeMarket.address, await vaiToken.underlying()], + }); + + const CallerFactory = await ethers.getContractFactory("DeviationBoundedOracleCaller", admin); + caller = await CallerFactory.deploy(oracle.address); + }); + + // Helper: initialize asset and set a specific min/max window via keeper updates + const initAssetWithWindow = async ( + asset: string, + minPrice: BigNumber = MIN_PRICE, + maxPrice: BigNumber = MAX_PRICE, + cooldown: number = DEFAULT_COOLDOWN, + triggerThreshold: BigNumber = DEFAULT_THRESHOLD, + resetThreshold: BigNumber = DEFAULT_RESET_THRESHOLD, + ) => { + await oracle.setTokenConfig({ + asset, + cooldownPeriod: cooldown, + triggerThreshold, + resetThreshold, + enableBoundedPricing: true, + enableCaching: true, + }); + await oracle.updateMinPrice(asset, minPrice); + await oracle.updateMaxPrice(asset, maxPrice); + }; + + // Helper: trigger protection via pump on an asset with window + const triggerPump = async ( + asset: string, + vToken: VBEP20Harness, + minPrice: BigNumber = MIN_PRICE, + threshold: BigNumber = DEFAULT_THRESHOLD, + ): Promise => { + const pumpSpot = minPrice.mul(EXP_SCALE.add(threshold)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(asset).returns(pumpSpot); + await oracle.getBoundedCollateralPrice(vToken.address); + return pumpSpot; + }; + + // Helper: disable protection by raising reset threshold above current range + const disableProtection = async (asset: string) => { + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + const state = await oracle.assetProtectionConfig(asset); + const rangeRatio = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const newReset = rangeRatio.add(1); + const newTrigger = newReset.add(parseUnits("0.01", 18)); + + await oracle.setThresholds(asset, newTrigger, newReset); + await oracle.exitProtectionMode(asset); + }; + + // ──────────────────────────────────────────────────────────────────────── + // E2E-1. updateProtectionState → View Price Functions (transient cache) + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-1: updateProtectionState → view price functions (transient cache)", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("1a: cache hit, no protection — view returns cached spot", async () => { + // updateAndGetBothPrices calls updateProtectionState then views in same tx + const tx = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(tx.collateral).to.equal(SPOT_PRICE); + expect(tx.debt).to.equal(SPOT_PRICE); + }); + + it("1b: cache hit, protection already active — view returns bounded prices", async () => { + // Trigger protection in prior tx + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Now updateProtectionState → view in same tx + // Protection already active, _checkAndTriggerProtection returns early. + // Cached collateral = min(pumpSpot, MIN_PRICE) = MIN_PRICE + // Cached debt = max(pumpSpot, maxPrice_after_expansion) = pumpSpot (since max was expanded) + const stateAfter = await oracle.assetProtectionConfig(assetA); + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(MIN_PRICE); + expect(result.debt).to.equal(stateAfter.maxPrice); + }); + + it("1c: cache hit, protection triggered + window expanded in same call", async () => { + // Spot drops far below MIN_PRICE — triggers protection in same updateProtectionState call + const crashSpot = parseUnits("0.7", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(crashSpot); + + // After expansion: newMin = crashSpot = 0.7, MAX_PRICE unchanged = 1.1 + // crashSpot < MAX_PRICE*(1-threshold) = 1.1 * 0.8 = 0.88 → 0.7 < 0.88 → triggers + // collateral = min(crashSpot, newMin) = min(0.7, 0.7) = 0.7 + // debt = max(crashSpot, MAX_PRICE) = max(0.7, 1.1) = 1.1 + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(crashSpot); + expect(result.debt).to.equal(MAX_PRICE); + + // Execute to verify state mutation + await caller.updateAndGetBothPrices(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + const stateAfter = await oracle.assetProtectionConfig(assetA); + expect(stateAfter.minPrice).to.equal(crashSpot); + }); + + it("1d: cache miss, no deviation — view fetches fresh, returns spot", async () => { + // Call view without prior updateProtectionState → no cache → fresh oracle fetch + const result = await caller.getViewPricesWithoutUpdate(vTokenA.address); + expect(result.collateral).to.equal(SPOT_PRICE); + expect(result.debt).to.equal(SPOT_PRICE); + }); + + it("1e: cache miss, exceeds deviation — simulated trigger returns bounded price", async () => { + // pumpSpot = MIN_PRICE * (1 + threshold) + 1 = 0.9 * 1.2 + 1wei = 1.08e18 + 1 + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + + // View without update → simulated trigger + const result = await caller.getViewPricesWithoutUpdate(vTokenA.address); + // collateral = min(pumpSpot, MIN_PRICE) = MIN_PRICE + expect(result.collateral).to.equal(MIN_PRICE); + // pumpSpot ≈ 1.08, MAX_PRICE = 1.1 → max(1.08, 1.1) = 1.1 + expect(result.debt).to.equal(MAX_PRICE); + + // Verify no state mutation + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("1f: per-asset cache isolation — update A, view B has no cache", async () => { + const specialSpot = parseUnits("2", 18); + // Set spot for B before init so setTokenConfig seeds min=max=2.0 + resilientOracle.getPrice.whenCalledWith(assetB).returns(specialSpot); + await initAssetWithWindow(assetB, parseUnits("1.95", 18), parseUnits("2.05", 18)); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + // Update assetA, then view assetB — B should fetch fresh from oracle + const collateralA = await caller.callStatic.updateAndGetCollateralPrice(vTokenA.address); + expect(collateralA).to.equal(SPOT_PRICE); + + // View B independently (no cache) + const resultB = await caller.getViewPricesWithoutUpdate(vTokenB.address); + expect(resultB.collateral).to.equal(specialSpot); + }); + + it("1g: non-whitelisted — updateProtectionState caches (spot, spot), views return cached spot", async () => { + await oracle.setAssetBoundedPricingEnabled(assetA, false); + const specialSpot = parseUnits("1.5", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(specialSpot); + + // updateProtectionState fetches spot and caches (spot, spot); views return from cache + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(specialSpot); + expect(result.debt).to.equal(specialSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-2. Protection Disabled → Extreme Price Move + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-2: protection disabled → extreme price move", () => { + it("2a: non-view re-triggers after disable", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Disable protection (raise reset threshold to allow) + await disableProtection(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + // Extreme price move — another pump + // After disable, window: min=0.9, max=pumpSpot (~1.08) + // New pump must exceed upperBound = 0.9 * 1.2 = 1.08 + // Use 1.2 — well above threshold + const newPumpSpot = parseUnits("1.2", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(newPumpSpot); + + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Returns conservative price: min(1.2, 0.9) = 0.9 + const price = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(price).to.equal(MIN_PRICE); + }); + + it("2b: view simulates trigger after disable", async () => { + await initAssetWithWindow(assetA); + + // Trigger + disable + await triggerPump(assetA, vTokenA); + await disableProtection(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + // Extreme price move — 1.2 exceeds upperBound 1.08 + const newPumpSpot = parseUnits("1.2", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(newPumpSpot); + + // View simulates trigger — returns bounded price even though currentlyUsingProtectedPrice is false + const collateral = await oracle.getBoundedCollateralPriceView(vTokenA.address); + expect(collateral).to.equal(MIN_PRICE); + + // currentlyUsingProtectedPrice still false (view doesn't mutate) + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("2c: lastProtectionTriggeredAt is fresh after re-trigger", async () => { + await initAssetWithWindow(assetA); + + // Trigger + await triggerPump(assetA, vTokenA); + const firstState = await oracle.assetProtectionConfig(assetA); + const firstTriggerTime = firstState.lastProtectionTriggeredAt; + + // Disable + await disableProtection(assetA); + + // Re-trigger with 1.2 + const newPumpSpot = parseUnits("1.2", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(newPumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const secondState = await oracle.assetProtectionConfig(assetA); + expect(secondState.lastProtectionTriggeredAt).to.be.gt(firstTriggerTime); + }); + + it("2d: repeated trigger → disable → trigger cycle", async () => { + await initAssetWithWindow(assetA); + + for (let cycle = 0; cycle < 3; cycle += 1) { + // Use triggerPump which computes minimal pumpSpot from current threshold + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Disable (may raise thresholds to allow exit), then restore to defaults + await disableProtection(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + // Restore thresholds to defaults using single setThresholds call + await oracle.setThresholds(assetA, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD); + } + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-3. Threshold Update Effects on Prices + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-3: threshold update effects on prices", () => { + // Spot = 1.15 — triggers at 20% (upperBound = 0.9*1.2 = 1.08) but not at 30% (0.9*1.3 = 1.17) + const spot = parseUnits("1.15", 18); + + it("3a: lowering threshold triggers previously-safe asset", async () => { + // Initialize with 30% threshold → upperBound = 0.9 * 1.3 = 1.17 + const highThreshold = parseUnits("0.3", 18); + const resetThreshold = parseUnits("0.15", 18); + await oracle.setTokenConfig({ + asset: assetA, + cooldownPeriod: DEFAULT_COOLDOWN, + triggerThreshold: highThreshold, + resetThreshold, + enableBoundedPricing: true, + enableCaching: true, + }); + await oracle.updateMinPrice(assetA, MIN_PRICE); + await oracle.updateMaxPrice(assetA, MAX_PRICE); + resilientOracle.getPrice.whenCalledWith(assetA).returns(spot); + + // Spot 1.15 < 1.17 → no trigger at 30% + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + // Lower trigger threshold to 20%, keep reset at current value + const state = await oracle.assetProtectionConfig(assetA); + await oracle.setThresholds(assetA, DEFAULT_THRESHOLD, state.resetThreshold); + + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + + it("3b: raising threshold prevents trigger", async () => { + await initAssetWithWindow(assetA); + resilientOracle.getPrice.whenCalledWith(assetA).returns(spot); + + // Raise trigger threshold to 30%, keep reset at current value + const state = await oracle.assetProtectionConfig(assetA); + await oracle.setThresholds(assetA, parseUnits("0.3", 18), state.resetThreshold); + + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + // Verify spot is returned + const price = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(price).to.equal(spot); + }); + + it("3c: view reflects threshold change immediately", async () => { + await initAssetWithWindow(assetA); + resilientOracle.getPrice.whenCalledWith(assetA).returns(spot); + + // At 20% threshold → upperBound = 1.08, spot 1.15 > 1.08 → view returns bounded + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(MIN_PRICE); + + // Raise to 30% → upperBound = 1.17, spot 1.15 < 1.17 → view returns spot + const state = await oracle.assetProtectionConfig(assetA); + await oracle.setThresholds(assetA, parseUnits("0.3", 18), state.resetThreshold); + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(spot); + }); + + it("3d: reset threshold interaction with exitProtectionMode", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Current range is wide, reset threshold is 10% — too low to disable + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "PriceRangeNotConverged"); + + // Raise reset threshold to allow disable via setThresholds + const state = await oracle.assetProtectionConfig(assetA); + const rangeRatio = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const newReset = rangeRatio.add(parseUnits("0.001", 18)); + const newTrigger = newReset.add(parseUnits("0.01", 18)); + + await oracle.setThresholds(assetA, newTrigger, newReset); + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-4. Protection Already Active — No Re-trigger, Window Still Expands + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-4: no re-trigger when active, window still expands", () => { + it("4a: continued deviation updates lastProtectionTriggeredAt and re-emits event", async () => { + await initAssetWithWindow(assetA); + + // Trigger + const pumpSpot = await triggerPump(assetA, vTokenA); + const stateAfterTrigger = await oracle.assetProtectionConfig(assetA); + const triggerTime = stateAfterTrigger.lastProtectionTriggeredAt; + + // Move price even further — deviation still exceeded, so event re-emitted and timestamp updated + const biggerPump = pumpSpot.add(parseUnits("0.5", 18)); + resilientOracle.getPrice.whenCalledWith(assetA).returns(biggerPump); + + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + + const stateAfter = await oracle.assetProtectionConfig(assetA); + expect(stateAfter.lastProtectionTriggeredAt).to.be.gte(triggerTime); + }); + + it("4b: window expands during active protection, deviation re-emits event", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + // Drop below min → min expands, deviation exceeded → event re-emitted + const lowSpot = parseUnits("0.8", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(lowSpot); + const tx1 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx1).to.emit(oracle, "MinPriceUpdated").withArgs(assetA, MIN_PRICE, lowSpot); + await expect(tx1).to.emit(oracle, "ProtectionTriggered"); + + // Rise above current max → max expands, deviation exceeded → event re-emitted + const stateAfterMin = await oracle.assetProtectionConfig(assetA); + const highSpot = stateAfterMin.maxPrice.add(parseUnits("0.5", 18)); + resilientOracle.getPrice.whenCalledWith(assetA).returns(highSpot); + const tx2 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx2).to.emit(oracle, "MaxPriceUpdated"); + await expect(tx2).to.emit(oracle, "ProtectionTriggered"); + }); + + it("4c: bounded prices reflect expanded window", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + // Expand min downward + const lowSpot = parseUnits("0.8", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(lowSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + // Now collateral should use new min (0.8), not old min (0.9) + // spot = 0.8, min = 0.8 → collateral = min(0.8, 0.8) = 0.8 + const price = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(price).to.equal(lowSpot); + + // Put spot back between new min and max + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + const collateralMid = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + // spot=1.0, min=0.8 → collateral = min(1.0, 0.8) = 0.8 + expect(collateralMid).to.equal(lowSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-5. Keeper During Protection + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-5: keeper updates during protection", () => { + it("5a: keeper updates succeed during active protection", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + await expect(oracle.updateMinPrice(assetA, parseUnits("0.85", 18))).to.not.be.reverted; + await expect(oracle.updateMaxPrice(assetA, parseUnits("1.15", 18))).to.not.be.reverted; + }); + + it("5b: keeper update succeeds after disable", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await disableProtection(assetA); + + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + await expect(oracle.updateMinPrice(assetA, parseUnits("0.85", 18))).to.not.be.reverted; + await expect(oracle.updateMaxPrice(assetA, parseUnits("1.15", 18))).to.not.be.reverted; + }); + + it("5c: keeper min update during protection affects bounded prices", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Keeper lowers min during active protection + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + const newMin = parseUnits("0.85", 18); + await oracle.updateMinPrice(assetA, newMin); + + // Bounded collateral should now use new min: min(1.0, 0.85) = 0.85 + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(collateral).to.equal(newMin); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-6. Collateral vs Debt Price Divergence Under Protection + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-6: collateral vs debt price divergence under protection", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + // Trigger protection + await triggerPump(assetA, vTokenA); + }); + + it("6a: spot between min and max — collateral = MIN_PRICE, debt = maxPrice", async () => { + // Set spot to 1.0 (between 0.9 and expanded max) + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + + // Protected: collateral = min(1.0, 0.9) = 0.9 + expect(collateral).to.equal(MIN_PRICE); + // Protected: debt = max(1.0, expandedMax). expandedMax > 1.0 → expandedMax + const state = await oracle.assetProtectionConfig(assetA); + expect(debt).to.equal(state.maxPrice); + expect(collateral).to.not.equal(debt); + }); + + it("6b: spot below min — collateral = spot, debt = maxPrice", async () => { + const lowSpot = parseUnits("0.8", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(lowSpot); + + // First call expands min to 0.8 + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + + // min(0.8, 0.8) = 0.8 + expect(collateral).to.equal(lowSpot); + // max(0.8, expandedMax) = expandedMax + const state = await oracle.assetProtectionConfig(assetA); + expect(debt).to.equal(state.maxPrice); + }); + + it("6c: spot above max — collateral = MIN_PRICE, debt = spot", async () => { + // Spot above the already-expanded max + const state = await oracle.assetProtectionConfig(assetA); + const highSpot = state.maxPrice.add(parseUnits("0.5", 18)); + resilientOracle.getPrice.whenCalledWith(assetA).returns(highSpot); + + // First call expands max + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + + // collateral = min(highSpot, MIN_PRICE) = MIN_PRICE + expect(collateral).to.equal(MIN_PRICE); + // max was expanded to highSpot. debt = max(highSpot, highSpot) = highSpot + expect(debt).to.equal(highSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-7. Multiple Assets Independence + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-7: multiple assets independence", () => { + it("7a: trigger on one, other unaffected", async () => { + await initAssetWithWindow(assetA); + await initAssetWithWindow(assetB); + + // Trigger protection on A with pump spot > upperBound (0.9 * 1.2 = 1.08) + const pumpSpot = parseUnits("1.2", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + resilientOracle.getPrice.whenCalledWith(assetB).returns(SPOT_PRICE); + + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + expect(await oracle.currentlyUsingProtectedPrice(assetB)).to.equal(false); + + // B returns spot unprotected + const priceB = await oracle.callStatic.getBoundedCollateralPrice(vTokenB.address); + expect(priceB).to.equal(SPOT_PRICE); + + // Keeper can still update B bounds + await expect(oracle.updateMinPrice(assetB, parseUnits("0.85", 18))).to.not.be.reverted; + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-8. setAssetBoundedPricingEnabled Round-Trip + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-8: setAssetBoundedPricingEnabled round-trip", () => { + it("8a: disable returns spot, re-enable resets window to current spot", async () => { + await initAssetWithWindow(assetA); + + // Set pump spot that would trigger: 1.2 > upperBound 1.08 + const pumpSpot = parseUnits("1.2", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + + // Disable bounded pricing → returns spot (not bounded) + await oracle.setAssetBoundedPricingEnabled(assetA, false); + const priceDisabled = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(priceDisabled).to.equal(pumpSpot); + + // Re-enable → window resets to current spot (min=max=pumpSpot) + await oracle.setAssetBoundedPricingEnabled(assetA, true); + // Fresh window at pumpSpot, no deviation → returns spot + const priceReEnabled = await oracle.getBoundedCollateralPriceView(vTokenA.address); + expect(priceReEnabled).to.equal(pumpSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-10. Multiple Sequential Calls in Same Transaction + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-10: multiple sequential calls in same transaction", () => { + it("10a: two consecutive non-view calls — second reflects updated window", async () => { + await initAssetWithWindow(assetA); + + // Pump spot > upperBound (1.08): use 1.2 + const pumpSpot = parseUnits("1.2", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + + // Both calls in same tx via caller + const result = await caller.callStatic.twoConsecutiveNonViewCollateral(vTokenA.address); + + // First call: triggers protection, collateral = min(1.2, 0.9) = 0.9 + expect(result.first).to.equal(MIN_PRICE); + // Second call: protection already active, no re-trigger. Same spot, same window. + // collateral = min(1.2, 0.9) = 0.9 + expect(result.second).to.equal(MIN_PRICE); + }); + + it("10b: updateProtectionState then non-view — non-view reads from cache", async () => { + await initAssetWithWindow(assetA); + + // Pump spot > upperBound (1.08): use 1.2 + const pumpSpot = parseUnits("1.2", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot); + + // updateProtectionState caches, then non-view reads from cache + const result = await caller.callStatic.updateThenNonViewCollateral(vTokenA.address); + // collateral = min(1.2, 0.9) = 0.9 + expect(result).to.equal(MIN_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-11. Atomic Window Expansion + Protection Trigger + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-11: atomic window expansion + protection trigger", () => { + it("11a: spot jumps far above maxPrice — only pump triggers, not crash", async () => { + await initAssetWithWindow(assetA); + + // Spot jumps to 2.0 — far above MAX_PRICE (1.1) + const bigPump = parseUnits("2", 18); + resilientOracle.getPrice.whenCalledWith(assetA).returns(bigPump); + + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + + // Window expansion: max → 2.0 (spot > old max 1.1) + await expect(tx).to.emit(oracle, "MaxPriceUpdated").withArgs(assetA, MAX_PRICE, bigPump); + + // Protection triggered (pump: spot 2.0 > MIN_PRICE * 1.2 = 1.08) + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-12. Boundary Precision Tests + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-12: boundary precision tests", () => { + it("12a: deviation threshold strict inequality — exact boundary does NOT trigger", async () => { + await initAssetWithWindow(assetA); + + // upperBound = MIN_PRICE * (1 + threshold) = 0.9 * 1.2 = 1.08e18 + const upperBound = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE); + resilientOracle.getPrice.whenCalledWith(assetA).returns(upperBound); + + // Exact boundary → should NOT trigger (uses > not >=) + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + // boundary + 1 → triggers + resilientOracle.getPrice.whenCalledWith(assetA).returns(upperBound.add(1)); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + + it("12b: reset threshold strict inequality — exact value reverts, minus 1 succeeds", async () => { + await initAssetWithWindow(assetA); + + // Trigger + await triggerPump(assetA, vTokenA); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + const state = await oracle.assetProtectionConfig(assetA); + const rangeRatio = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + + // Set resetThreshold = rangeRatio via setThresholds → exitProtectionMode reverts (>=) + const triggerThreshold = rangeRatio.add(parseUnits("0.01", 18)); + await oracle.setThresholds(assetA, triggerThreshold, rangeRatio); + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "PriceRangeNotConverged"); + + // Set resetThreshold = rangeRatio + 1 → succeeds + if (rangeRatio.add(1).lt(triggerThreshold)) { + await oracle.setThresholds(assetA, triggerThreshold, rangeRatio.add(1)); + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + } + }); + + it("12c: deadband strict inequality — exact value returns false, +1 returns true", async () => { + await initAssetWithWindow(assetA); + + // proposedMin such that drift = exactly KEEPER_DEADBAND (5%) + // drift = |current - proposed| * 1e18 / current = KEEPER_DEADBAND + // proposed = current * (1e18 - KEEPER_DEADBAND) / 1e18 = 0.9 * 0.95 = 0.855 + const proposedMinExact = MIN_PRICE.mul(EXP_SCALE.sub(KEEPER_DEADBAND)).div(EXP_SCALE); + + const [needsMinExact] = await oracle.checkAndGetWindowDrift([assetA], [proposedMinExact], [MAX_PRICE]); + expect(needsMinExact[0]).to.equal(false); + + // proposed - 1 → drift slightly above deadband + const proposedMinPlusOne = proposedMinExact.sub(1); + const [needsMinPlus] = await oracle.checkAndGetWindowDrift([assetA], [proposedMinPlusOne], [MAX_PRICE]); + expect(needsMinPlus[0]).to.equal(true); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-13. Transient Cache Call-Count Verification + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-13: transient cache call-count verification", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + resilientOracle.getPrice.reset(); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + }); + + it("13a: cache hit — update + both views makes only 1 oracle call", async () => { + await caller.updateAndGetBothPrices(vTokenA.address); + + // updateProtectionState fetches spot once; both views read from transient cache + expect(resilientOracle.getPrice).to.have.callCount(1); + }); + + it("13b: cache miss — views without update make 2 oracle calls", async () => { + // Non-view wrapper so smock records the calls + await caller.getViewPricesWithoutUpdateNonView(vTokenA.address); + + // No prior updateProtectionState → cache empty → each view fetches independently + expect(resilientOracle.getPrice).to.have.callCount(2); + }); + + it("13c: non-view after update uses cache — update + non-view makes 1 oracle call", async () => { + await caller.updateThenNonViewCollateral(vTokenA.address); + + // updateProtectionState fetches once and caches; getBoundedCollateralPrice reads from cache + expect(resilientOracle.getPrice).to.have.callCount(1); + }); + + it("13d: two consecutive non-view calls — 1 oracle call (second reads cache)", async () => { + await caller.twoConsecutiveNonViewCollateral(vTokenA.address); + + // First getBoundedCollateralPrice fetches and caches; second reads from cache + expect(resilientOracle.getPrice).to.have.callCount(1); + }); + + it("13e: per-asset cache isolation — update A, view B still calls oracle", async () => { + const spotB = parseUnits("2", 18); + // Set spot for B before init so setTokenConfig seeds and keeper updates succeed + resilientOracle.getPrice.whenCalledWith(assetB).returns(SPOT_PRICE); + await initAssetWithWindow(assetB); + + resilientOracle.getPrice.reset(); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + resilientOracle.getPrice.whenCalledWith(assetB).returns(spotB); + + // updateProtectionState(A) caches A's prices; view(B) has no cache → fetches fresh + await caller.updateAViewB(vTokenA.address, vTokenB.address); + + // 1 call for A (update), 1 call for B (cache miss view) = 2 total + expect(resilientOracle.getPrice).to.have.callCount(2); + }); + + it("13f: cache hit under active protection — still only 1 oracle call", async () => { + // Trigger protection in prior tx + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Reset call tracking after setup + resilientOracle.getPrice.reset(); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + await caller.updateAndGetBothPrices(vTokenA.address); + + // Even under active protection, updateProtectionState caches both prices → views use cache + expect(resilientOracle.getPrice).to.have.callCount(1); + }); + + it("13g: non-whitelisted — update fetches+caches, views read cache = 1 oracle call", async () => { + await oracle.setAssetBoundedPricingEnabled(assetA, false); + + resilientOracle.getPrice.reset(); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + await caller.updateAndGetBothPrices(vTokenA.address); + + // updateProtectionState fetches spot and caches (spot, spot). Views read from cache. + expect(resilientOracle.getPrice).to.have.callCount(1); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-14. getBoundedPrices Dual-Price API + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-14: getBoundedPrices dual-price API", () => { + it("14a: updateProtectionState → getBoundedPricesView returns both cached prices", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Set a spot between min and max + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + // updateProtectionState caches, then getBoundedPricesView reads cache + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + const state = await oracle.assetProtectionConfig(assetA); + + // collateral = min(spot, minPrice) = min(1.0, 0.9) = 0.9 + expect(result.collateral).to.equal(MIN_PRICE); + // debt = max(spot, maxPrice) = maxPrice (expanded) + expect(result.debt).to.equal(state.maxPrice); + }); + + it("14b: getBoundedPrices (non-view) returns same as individual functions", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection + await triggerPump(assetA, vTokenA); + + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + // Get prices via individual functions + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + + // Get prices via dual API + const result = await oracle.callStatic.getBoundedPrices(vTokenA.address); + + expect(result.collateralPrice).to.equal(collateral); + expect(result.debtPrice).to.equal(debt); + }); + + it("14c: getBoundedPricesView matches individual view functions", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection so prices diverge + await triggerPump(assetA, vTokenA); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + // Get prices via individual view functions + const collateral = await oracle.getBoundedCollateralPriceView(vTokenA.address); + const debt = await oracle.getBoundedDebtPriceView(vTokenA.address); + + // Get prices via dual view API + const result = await oracle.getBoundedPricesView(vTokenA.address); + + expect(result.collateralPrice).to.equal(collateral); + expect(result.debtPrice).to.equal(debt); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-15. Keeper Updates During Active Protection + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-15: keeper updates during active protection", () => { + it("15a: trigger → keeper updates min → bounded collateral reflects new min", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Spot is between min and max + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + // Verify collateral = MIN_PRICE before keeper update + const collateralBefore = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(collateralBefore).to.equal(MIN_PRICE); + + // Keeper lowers min + const newMin = parseUnits("0.85", 18); + await oracle.updateMinPrice(assetA, newMin); + + // Bounded collateral now uses new min: min(1.0, 0.85) = 0.85 + const collateralAfter = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(collateralAfter).to.equal(newMin); + }); + + it("15b: trigger → keeper updates max → bounded debt reflects new max", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Record original max + const stateBefore = await oracle.assetProtectionConfig(assetA); + const originalMax = stateBefore.maxPrice; + + // Spot is between min and max + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + + // Verify debt = originalMax before keeper update + const debtBefore = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(debtBefore).to.equal(originalMax); + + // Keeper raises max + const newMax = originalMax.add(parseUnits("0.1", 18)); + await oracle.updateMaxPrice(assetA, newMax); + + // Bounded debt now uses new max: max(1.0, newMax) = newMax + const debtAfter = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(debtAfter).to.equal(newMax); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-16. Volatile Price Extends Protection Period + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-16: volatile price extends protection period", () => { + it("16a: continued deviation extends cooldown, blocks early disable", async () => { + await initAssetWithWindow(assetA); + + // Trigger protection + const pumpSpot = await triggerPump(assetA, vTokenA); + const firstTriggerTime = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + // Advance half cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // Another deviating price → updates lastProtectionTriggeredAt + const biggerPump = pumpSpot.add(parseUnits("0.2", 18)); + resilientOracle.getPrice.whenCalledWith(assetA).returns(biggerPump); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + + const secondTriggerTime = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + expect(secondTriggerTime).to.be.gt(firstTriggerTime); + + // Advance half cooldown again — total = cooldown from initial but only half from latest + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // Raise reset threshold so range check passes, but cooldown should block + const state = await oracle.assetProtectionConfig(assetA); + const range = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const trigger = state.triggerThreshold; + if (newReset.gte(trigger)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, trigger, newReset); + } + + // Disable should revert — cooldown restarted from second trigger + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + + // Advance remaining half cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2 + 1]); + await ethers.provider.send("evm_mine", []); + + // Now disable succeeds + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + }); + + it("16b: price normalizes — no event, timestamp unchanged, protection still active", async () => { + await initAssetWithWindow(assetA); + + await triggerPump(assetA, vTokenA); + const triggerTime = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + // Price returns within threshold + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + + await expect(tx).to.not.emit(oracle, "ProtectionTriggered"); + expect((await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt).to.equal(triggerTime); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Bounded pricing still applies + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + expect(collateral).to.equal(MIN_PRICE); + }); + + it("16c: full volatile cycle — repeated spikes extend protection, normalization in between, final disable", async () => { + await initAssetWithWindow(assetA); + + // ── Cycle 1: initial trigger ── + const pumpSpot1 = await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + const trigger1 = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + // Price normalizes mid-cycle — protection still active, timestamp unchanged + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 4]); + await ethers.provider.send("evm_mine", []); + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + const txNorm1 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(txNorm1).to.not.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Bounded pricing still applies despite normal spot + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + + // ── Cycle 2: second spike extends cooldown (keep within window to avoid exceeding MAX_THRESHOLD) ── + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 4]); + await ethers.provider.send("evm_mine", []); + const pumpSpot2 = pumpSpot1.add(parseUnits("0.05", 18)); + resilientOracle.getPrice.whenCalledWith(assetA).returns(pumpSpot2); + const txSpike2 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(txSpike2).to.emit(oracle, "ProtectionTriggered"); + const trigger2 = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + expect(trigger2).to.be.gt(trigger1); + + // Try disable after original cooldown elapsed but not after second trigger + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2 + 1]); + await ethers.provider.send("evm_mine", []); + + // Raise reset threshold so range check passes + const stateM = await oracle.assetProtectionConfig(assetA); + const rangeM = stateM.maxPrice.sub(stateM.minPrice).mul(EXP_SCALE).div(stateM.minPrice); + const newResetM = rangeM.add(parseUnits("0.001", 18)); + const triggerM = stateM.triggerThreshold; + if (newResetM.gte(triggerM)) { + await oracle.setThresholds(assetA, newResetM.add(parseUnits("0.01", 18)), newResetM); + } else { + await oracle.setThresholds(assetA, triggerM, newResetM); + } + + // Disable fails — cooldown restarted from second spike + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + + // Price normalizes again — still protected + resilientOracle.getPrice.whenCalledWith(assetA).returns(SPOT_PRICE); + const txNorm2 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(txNorm2).to.not.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + + // ── Final: wait full cooldown from last spike, disable succeeds ── + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN]); + await ethers.provider.send("evm_mine", []); + + await oracle.exitProtectionMode(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + expect((await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt).to.equal(0); + + // Spot prices returned after disable + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(collateral).to.equal(SPOT_PRICE); + expect(debt).to.equal(SPOT_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-18. Keeper batch (syncPriceBoundsAndProtections): converge + exit in one tx + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-18: keeper converges and exits in a single batched tx", () => { + it("18a: pump trigger → spot stabilises → batch (SetMin, SetMax, Exit) clears protection in one tx", async () => { + // KeeperAction enum: 0 = SetMinPrice, 1 = SetMaxPrice, 2 = ExitProtectionMode + const SetMinPrice = 0; + const SetMaxPrice = 1; + const ExitProtectionMode = 2; + + await initAssetWithWindow(assetA); + + // 1. Trigger via pump + const pumpSpot = await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // 2. Spot stabilises somewhere inside the post-trigger window + const stableSpot = MIN_PRICE.add(pumpSpot).div(2); + resilientOracle.getPrice.whenCalledWith(assetA).returns(stableSpot); + + // 3. Wait out cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // 4. Single batched tx — converge window to spot and exit protection + const tx = await oracle.syncPriceBoundsAndProtections([ + { asset: assetA, action: SetMinPrice, value: stableSpot }, + { asset: assetA, action: SetMaxPrice, value: stableSpot }, + { asset: assetA, action: ExitProtectionMode, value: 0 }, + ]); + + await expect(tx) + .to.emit(oracle, "MinPriceUpdated") + .and.to.emit(oracle, "MaxPriceUpdated") + .and.to.emit(oracle, "ProtectionModeExited") + .withArgs(assetA); + + // 5. Protection cleared, bounded prices fall back to spot + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(collateral).to.equal(stableSpot); + expect(debt).to.equal(stableSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────────── + // E2E-17. Keeper convergence path: trigger → min == max == spot → exitProtectionMode + // ──────────────────────────────────────────────────────────────────────── + + describe("E2E-17: keeper convergence to min == max == spot enables clean exit", () => { + it("17a: full convergence path — pump trigger, keeper converges window to spot, exit succeeds", async () => { + await initAssetWithWindow(assetA); + + // 1. Trigger protection via pump + const pumpSpot = await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // 2. Spot stabilises somewhere inside the post-trigger window (between minPrice and maxPrice) + const stableSpot = MIN_PRICE.add(pumpSpot).div(2); + resilientOracle.getPrice.whenCalledWith(assetA).returns(stableSpot); + + // 3. Keeper converges the window: first set min = stableSpot (allowed; old maxPrice >= stableSpot) + await oracle.updateMinPrice(assetA, stableSpot); + let state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(stableSpot); + + // 4. Keeper sets max = stableSpot — newly allowed (newMax == minPrice == spot) + await oracle.updateMaxPrice(assetA, stableSpot); + state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(stableSpot); + expect(state.maxPrice).to.equal(stableSpot); + + // 5. Range ratio collapses to zero, satisfying the resetThreshold gate + const rangeRatio = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + expect(rangeRatio).to.equal(0); + + // 6. Cooldown elapses and exitProtectionMode succeeds without governance threshold gymnastics + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + const tx = await oracle.exitProtectionMode(assetA); + await expect(tx).to.emit(oracle, "ProtectionModeExited").withArgs(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + // 7. Bounded prices fall back to spot once protection is cleared + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + expect(collateral).to.equal(stableSpot); + expect(debt).to.equal(stableSpot); + }); + }); +}); diff --git a/test/fork/DeviationBoundedOracle.ts b/test/fork/DeviationBoundedOracle.ts new file mode 100644 index 00000000..863c4927 --- /dev/null +++ b/test/fork/DeviationBoundedOracle.ts @@ -0,0 +1,1959 @@ +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; +import chai from "chai"; +import { BigNumber } from "ethers"; +import { parseUnits } from "ethers/lib/utils"; +import { ethers, upgrades } from "hardhat"; + +import { ADDRESSES } from "../../helpers/deploymentConfig"; +import { + DeviationBoundedOracle, + DeviationBoundedOracleCaller, + IAccessControlManagerV8, + MockSimpleOracle, + VBep20Interface, +} from "../../typechain-types"; +import { addr0000 } from "../utils/data"; +import { forking, initMainnetUser } from "./utils"; + +const { expect } = chai; + +const FORK: boolean = process.env.FORK === "true"; +const FORKED_NETWORK: string = process.env.FORKED_NETWORK || ""; + +const EXP_SCALE = parseUnits("1", 18); +const MIN_THRESHOLD = parseUnits("0.05", 18); +const MAX_THRESHOLD = parseUnits("0.5", 18); +const KEEPER_DEADBAND = parseUnits("0.05", 18); +const DEFAULT_THRESHOLD = parseUnits("0.2", 18); +const DEFAULT_RESET_THRESHOLD = parseUnits("0.1", 18); +const DEFAULT_COOLDOWN = 3600; +const SPOT_PRICE = parseUnits("1", 18); +const MIN_PRICE = parseUnits("0.9", 18); +const MAX_PRICE = parseUnits("1.1", 18); +const NATIVE_TOKEN_ADDR = "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"; + +// Real BSC mainnet vToken addresses +const VETH_ADDRESS = "0xf508fCD89b8bd15579dc79A6827cB4686A3592c8"; +const VBTC_ADDRESS = "0x882C173bC7Ff3b7786CA16dfeD3DFFfb9Ee7847B"; + +if (FORK && FORKED_NETWORK === "bscmainnet") { + const { + acm: ACM_ADDRESS, + timelock: TIMELOCK_ADDRESS, + vBNBAddress: VBNB_ADDRESS, + VAIAddress: VAI_ADDRESS, + } = ADDRESSES[FORKED_NETWORK]; + + forking(90924377, () => { + let someone: SignerWithAddress; + let mockOracle: MockSimpleOracle; + let acm: IAccessControlManagerV8; + let oracle: DeviationBoundedOracle; + let caller: DeviationBoundedOracleCaller; + let vTokenA: VBep20Interface; // vETH + let vTokenB: VBep20Interface; // vBTC + let assetA: string; // WETH underlying + let assetB: string; // BTCB underlying + + const fixture = async () => { + const [deployer, other] = await ethers.getSigners(); + + // Impersonate the real BSC mainnet timelock + const impersonatedTimelock = await initMainnetUser(TIMELOCK_ADDRESS, parseUnits("100")); + + // Get real on-chain ACM + const acmContract = ( + await ethers.getContractAt("IAccessControlManagerV8", ACM_ADDRESS, impersonatedTimelock) + ); + + // Get real on-chain vTokens + const vETH = await ethers.getContractAt("VBep20Interface", VETH_ADDRESS); + const vBTC = await ethers.getContractAt("VBep20Interface", VBTC_ADDRESS); + const underlyingA = await vETH.underlying(); + const underlyingB = await vBTC.underlying(); + + // Deploy MockSimpleOracle (fresh — replaces ResilientOracle for price control) + const MockOracleFactory = await ethers.getContractFactory("MockSimpleOracle"); + const mockOracleDeployed = await MockOracleFactory.deploy(); + + // Set default prices for all test assets + await mockOracleDeployed.setPrice(underlyingA, SPOT_PRICE); + await mockOracleDeployed.setPrice(underlyingB, SPOT_PRICE); + await mockOracleDeployed.setPrice(NATIVE_TOKEN_ADDR, SPOT_PRICE); + await mockOracleDeployed.setPrice(VAI_ADDRESS, SPOT_PRICE); + + // Deploy DeviationBoundedOracle as upgradeable proxy + const OracleFactory = await ethers.getContractFactory("DeviationBoundedOracle", deployer); + const oracleDeployed = await upgrades.deployProxy(OracleFactory, [acmContract.address], { + constructorArgs: [mockOracleDeployed.address, VBNB_ADDRESS, VAI_ADDRESS], + }); + + // Impersonated timelock grants all DBO permissions to deployer via real ACM + const DBO_FUNCTIONS = [ + "setTokenConfig((address,uint64,uint256,uint256,bool,bool))", + "setTokenConfigs((address,uint64,uint256,uint256,bool,bool)[])", + "setCooldownPeriod(address,uint64)", + "setThresholds(address,uint256,uint256)", + "setAssetBoundedPricingEnabled(address,bool)", + "setCachingEnabled(address,bool)", + "updateMinPrice(address,uint128)", + "updateMaxPrice(address,uint128)", + "exitProtectionMode(address)", + "syncPriceBoundsAndProtections((address,uint8,uint256)[])", + ]; + for (const fn of DBO_FUNCTIONS) { + await acmContract.giveCallPermission(oracleDeployed.address, fn, deployer.address); + } + + // Deploy caller as normal contract + const CallerFactory = await ethers.getContractFactory("DeviationBoundedOracleCaller", deployer); + const callerDeployed = await CallerFactory.deploy(oracleDeployed.address); + + return { + mockOracle: mockOracleDeployed, + acm: acmContract, + oracle: oracleDeployed, + caller: callerDeployed, + admin: deployer, + someone: other, + timelockSigner: impersonatedTimelock, + vTokenA: vETH, + vTokenB: vBTC, + assetA: underlyingA, + assetB: underlyingB, + }; + }; + + beforeEach(async () => { + ({ mockOracle, acm, oracle, caller, someone, vTokenA, vTokenB, assetA, assetB } = await loadFixture(fixture)); + }); + + // ── Helpers ─────────────────────────────────────────────────────────── + + const tokenCfg = ( + asset: string, + cooldownPeriod: number | BigNumber = DEFAULT_COOLDOWN, + triggerThreshold: BigNumber = DEFAULT_THRESHOLD, + resetThreshold: BigNumber = DEFAULT_RESET_THRESHOLD, + enableBoundedPricing = true, + enableCaching = true, + ) => ({ + asset, + cooldownPeriod, + triggerThreshold, + resetThreshold, + enableBoundedPricing, + enableCaching, + }); + + const initAsset = async ( + asset: string, + cooldown: number = DEFAULT_COOLDOWN, + triggerThreshold: BigNumber = DEFAULT_THRESHOLD, + resetThreshold: BigNumber = DEFAULT_RESET_THRESHOLD, + ) => { + await oracle.setTokenConfig(tokenCfg(asset, cooldown, triggerThreshold, resetThreshold)); + }; + + const initAssetWithWindow = async ( + asset: string, + minPrice: BigNumber = MIN_PRICE, + maxPrice: BigNumber = MAX_PRICE, + cooldown: number = DEFAULT_COOLDOWN, + triggerThreshold: BigNumber = DEFAULT_THRESHOLD, + resetThreshold: BigNumber = DEFAULT_RESET_THRESHOLD, + ) => { + await oracle.setTokenConfig(tokenCfg(asset, cooldown, triggerThreshold, resetThreshold)); + await oracle.updateMinPrice(asset, minPrice); + await oracle.updateMaxPrice(asset, maxPrice); + }; + + const triggerPump = async ( + asset: string, + vToken: VBep20Interface, + minPrice: BigNumber = MIN_PRICE, + threshold: BigNumber = DEFAULT_THRESHOLD, + ): Promise => { + const pumpSpot = minPrice.mul(EXP_SCALE.add(threshold)).div(EXP_SCALE).add(1); + await mockOracle.setPrice(asset, pumpSpot); + await oracle.getBoundedCollateralPrice(vToken.address); + return pumpSpot; + }; + + const disableProtection = async (asset: string) => { + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + const state = await oracle.assetProtectionConfig(asset); + const rangeRatio = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const newReset = rangeRatio.add(1); + const newTrigger = newReset.add(parseUnits("0.01", 18)); + + await oracle.setThresholds(asset, newTrigger, newReset); + await oracle.exitProtectionMode(asset); + }; + + // ──────────────────────────────────────────────────────────────────── + // 1. Constructor + // ──────────────────────────────────────────────────────────────────── + + describe("1. constructor", () => { + it("1.1 sets immutables correctly", async () => { + expect(await oracle.RESILIENT_ORACLE()).to.equal(mockOracle.address); + expect(await oracle.nativeMarket()).to.equal(VBNB_ADDRESS); + expect(await oracle.vai()).to.equal(VAI_ADDRESS); + }); + + it("1.2 reverts when _resilientOracle is zero address", async () => { + const Factory = await ethers.getContractFactory("DeviationBoundedOracle"); + await expect( + upgrades.deployProxy(Factory, [acm.address], { + constructorArgs: [addr0000, VBNB_ADDRESS, VAI_ADDRESS], + }), + ).to.be.revertedWithCustomError(oracle, "ZeroAddressNotAllowed"); + }); + + it("1.3 reverts when nativeMarketAddress is zero address", async () => { + const Factory = await ethers.getContractFactory("DeviationBoundedOracle"); + await expect( + upgrades.deployProxy(Factory, [acm.address], { + constructorArgs: [mockOracle.address, addr0000, VAI_ADDRESS], + }), + ).to.be.revertedWithCustomError(oracle, "ZeroAddressNotAllowed"); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 2. Initialize + // ──────────────────────────────────────────────────────────────────── + + describe("2. initialize", () => { + it("2.1 sets access control manager", async () => { + expect(ethers.utils.getAddress(await oracle.accessControlManager())).to.equal( + ethers.utils.getAddress(acm.address), + ); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 3. setTokenConfig + // ──────────────────────────────────────────────────────────────────── + + describe("3. setTokenConfig", () => { + describe("happy path", () => { + it("3.1 sets all struct fields, emits events, updates asset lists", async () => { + const tx = await oracle.setTokenConfig(tokenCfg(assetA)); + + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(SPOT_PRICE); + expect(state.maxPrice).to.equal(SPOT_PRICE); + expect(state.currentlyUsingProtectedPrice).to.equal(false); + expect(state.isBoundedPricingEnabled).to.equal(true); + expect(state.lastProtectionTriggeredAt).to.equal(0); + expect(state.cooldownPeriod).to.equal(DEFAULT_COOLDOWN); + expect(state.asset).to.equal(assetA); + expect(state.triggerThreshold).to.equal(DEFAULT_THRESHOLD); + expect(state.resetThreshold).to.equal(DEFAULT_RESET_THRESHOLD); + expect(state.cachingEnabled).to.equal(true); + + await expect(tx) + .to.emit(oracle, "ProtectionInitialized") + .withArgs(assetA, SPOT_PRICE, SPOT_PRICE, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, true); + + expect(await oracle.getInitializedAssets()).to.include(assetA); + expect(await oracle.getAllBoundedPricingEnabledAssets()).to.include(assetA); + }); + }); + + describe("revert branches", () => { + it("3.2 reverts when caller is unauthorized", async () => { + await expect(oracle.connect(someone).setTokenConfig(tokenCfg(assetA))).to.be.revertedWithCustomError( + oracle, + "Unauthorized", + ); + }); + + it("3.3 reverts when asset is zero address", async () => { + await expect(oracle.setTokenConfig(tokenCfg(addr0000))).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("3.4 reverts when already initialized", async () => { + await initAsset(assetA); + await expect(oracle.setTokenConfig(tokenCfg(assetA))).to.be.revertedWithCustomError( + oracle, + "MarketAlreadyInitialized", + ); + }); + + it("3.5 reverts when threshold < MIN_THRESHOLD", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, MIN_THRESHOLD.sub(1), DEFAULT_RESET_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "ThresholdBelowMinimum"); + }); + + it("3.6 reverts when threshold > MAX_THRESHOLD", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, MAX_THRESHOLD.add(1), DEFAULT_RESET_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "ThresholdAboveMaximum"); + }); + + it("3.7 reverts when resetThreshold >= triggerThreshold", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD, DEFAULT_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "InvalidResetThreshold"); + }); + + it("3.8 reverts when asset is VAI", async () => { + await expect(oracle.setTokenConfig(tokenCfg(VAI_ADDRESS))).to.be.revertedWithCustomError( + oracle, + "VAINotAllowed", + ); + }); + + it("3.9 reverts when cooldownPeriod is zero", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, 0, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "ZeroValueNotAllowed"); + }); + + it("3.10 reverts when triggerThreshold is zero", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, BigNumber.from(0), DEFAULT_RESET_THRESHOLD)), + ).to.be.revertedWithCustomError(oracle, "ZeroValueNotAllowed"); + }); + + it("3.11 reverts when resetThreshold is zero", async () => { + await expect( + oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD, BigNumber.from(0))), + ).to.be.revertedWithCustomError(oracle, "ZeroValueNotAllowed"); + }); + + it("3.12 reverts when re-initializing after de-whitelist", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + await expect(oracle.setTokenConfig(tokenCfg(assetA))).to.be.revertedWithCustomError( + oracle, + "MarketAlreadyInitialized", + ); + }); + + it("3.13 reverts with PriceExceedsUint128 when oracle returns > uint128 max", async () => { + const overflowPrice = BigNumber.from(2).pow(128); + await mockOracle.setPrice(assetA, overflowPrice); + await expect(oracle.setTokenConfig(tokenCfg(assetA))).to.be.revertedWithCustomError( + oracle, + "PriceExceedsUint128", + ); + }); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 4. setCooldownPeriod + // ──────────────────────────────────────────────────────────────────── + + describe("4. setCooldownPeriod", () => { + beforeEach(async () => { + await initAsset(assetA); + }); + + it("4.1 updates cooldownPeriod and emits event", async () => { + const tx = await oracle.setCooldownPeriod(assetA, 7200); + await expect(tx).to.emit(oracle, "CooldownPeriodSet").withArgs(assetA, DEFAULT_COOLDOWN, 7200); + expect((await oracle.assetProtectionConfig(assetA)).cooldownPeriod).to.equal(7200); + }); + + it("4.2 reverts when caller is unauthorized", async () => { + await expect(oracle.connect(someone).setCooldownPeriod(assetA, 7200)).to.be.revertedWithCustomError( + oracle, + "Unauthorized", + ); + }); + + it("4.3 reverts when asset is zero address", async () => { + await expect(oracle.setCooldownPeriod(addr0000, 7200)).to.be.revertedWithCustomError( + oracle, + "ZeroAddressNotAllowed", + ); + }); + + it("4.4 reverts when not initialized", async () => { + await expect(oracle.setCooldownPeriod(assetB, 7200)).to.be.revertedWithCustomError( + oracle, + "MarketNotInitialized", + ); + }); + + it("4.5 reverts when new cooldown is zero", async () => { + await expect(oracle.setCooldownPeriod(assetA, 0)).to.be.revertedWithCustomError(oracle, "ZeroValueNotAllowed"); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 5. setThresholds + // ──────────────────────────────────────────────────────────────────── + + describe("5. setThresholds", () => { + beforeEach(async () => { + await initAsset(assetA); + }); + + it("5.1 updates both thresholds and emits both events", async () => { + const newTrigger = parseUnits("0.25", 18); + const newReset = parseUnits("0.12", 18); + const tx = await oracle.setThresholds(assetA, newTrigger, newReset); + await expect(tx).to.emit(oracle, "TriggerThresholdSet").withArgs(assetA, DEFAULT_THRESHOLD, newTrigger); + await expect(tx).to.emit(oracle, "ResetThresholdSet").withArgs(assetA, DEFAULT_RESET_THRESHOLD, newReset); + }); + + it("5.2 only emits TriggerThresholdSet when only trigger changes", async () => { + const tx = await oracle.setThresholds(assetA, parseUnits("0.25", 18), DEFAULT_RESET_THRESHOLD); + await expect(tx).to.emit(oracle, "TriggerThresholdSet"); + await expect(tx).to.not.emit(oracle, "ResetThresholdSet"); + }); + + it("5.3 only emits ResetThresholdSet when only reset changes", async () => { + const tx = await oracle.setThresholds(assetA, DEFAULT_THRESHOLD, parseUnits("0.08", 18)); + await expect(tx).to.not.emit(oracle, "TriggerThresholdSet"); + await expect(tx).to.emit(oracle, "ResetThresholdSet"); + }); + + it("5.4 emits no events when neither changes", async () => { + const tx = await oracle.setThresholds(assetA, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD); + await expect(tx).to.not.emit(oracle, "TriggerThresholdSet"); + await expect(tx).to.not.emit(oracle, "ResetThresholdSet"); + }); + + it("5.5 reverts when caller is unauthorized", async () => { + await expect( + oracle.connect(someone).setThresholds(assetA, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("5.6 reverts when not initialized", async () => { + await expect( + oracle.setThresholds(assetB, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "MarketNotInitialized"); + }); + + it("5.7 reverts when trigger below MIN_THRESHOLD", async () => { + await expect( + oracle.setThresholds(assetA, MIN_THRESHOLD.sub(1), DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "ThresholdBelowMinimum"); + }); + + it("5.8 reverts when trigger above MAX_THRESHOLD", async () => { + await expect( + oracle.setThresholds(assetA, MAX_THRESHOLD.add(1), DEFAULT_RESET_THRESHOLD), + ).to.be.revertedWithCustomError(oracle, "ThresholdAboveMaximum"); + }); + + it("5.9 reverts when reset >= trigger", async () => { + await expect(oracle.setThresholds(assetA, DEFAULT_THRESHOLD, DEFAULT_THRESHOLD)).to.be.revertedWithCustomError( + oracle, + "InvalidResetThreshold", + ); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 6. setAssetBoundedPricingEnabled + // ──────────────────────────────────────────────────────────────────── + + describe("6. setAssetBoundedPricingEnabled", () => { + beforeEach(async () => { + await initAsset(assetA); + }); + + it("6.1 disables bounded pricing and emits event", async () => { + const tx = await oracle.setAssetBoundedPricingEnabled(assetA, false); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, false); + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(false); + }); + + it("6.2 enables bounded pricing and emits event", async () => { + await oracle.setAssetBoundedPricingEnabled(assetA, false); + const tx = await oracle.setAssetBoundedPricingEnabled(assetA, true); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, true); + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(true); + }); + + it("6.3 reverts when caller is unauthorized", async () => { + await expect( + oracle.connect(someone).setAssetBoundedPricingEnabled(assetA, false), + ).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("6.4 reverts when not initialized", async () => { + await expect(oracle.setAssetBoundedPricingEnabled(assetB, true)).to.be.revertedWithCustomError( + oracle, + "MarketNotInitialized", + ); + }); + + it("6.5 reverts when disabling with active protection", async () => { + await initAssetWithWindow(assetB); + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + await mockOracle.setPrice(assetB, pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenB.address); + + await expect(oracle.setAssetBoundedPricingEnabled(assetB, false)).to.be.revertedWithCustomError( + oracle, + "ProtectedPriceActive", + ); + }); + + it("6.6 price functions return spot after disabling", async () => { + await initAssetWithWindow(assetB); + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + await mockOracle.setPrice(assetB, pumpSpot); + + expect(await oracle.getBoundedCollateralPriceView(vTokenB.address)).to.equal(MIN_PRICE); + await oracle.setAssetBoundedPricingEnabled(assetB, false); + expect(await oracle.getBoundedCollateralPriceView(vTokenB.address)).to.equal(pumpSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 6b. setCachingEnabled + // ──────────────────────────────────────────────────────────────────── + + describe("6b. setCachingEnabled", () => { + beforeEach(async () => { + await initAsset(assetA); + }); + + it("6b.1 defaults to true at asset initialization", async () => { + const state = await oracle.assetProtectionConfig(assetA); + expect(state.cachingEnabled).to.equal(true); + }); + + it("6b.2 disables caching and emits CachingEnabledUpdated", async () => { + const tx = await oracle.setCachingEnabled(assetA, false); + await expect(tx).to.emit(oracle, "CachingEnabledUpdated").withArgs(assetA, true, false); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.cachingEnabled).to.equal(false); + }); + + it("6b.3 re-enables caching and emits CachingEnabledUpdated", async () => { + await oracle.setCachingEnabled(assetA, false); + const tx = await oracle.setCachingEnabled(assetA, true); + await expect(tx).to.emit(oracle, "CachingEnabledUpdated").withArgs(assetA, false, true); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.cachingEnabled).to.equal(true); + }); + + it("6b.4 reverts when caller is unauthorized", async () => { + await expect(oracle.connect(someone).setCachingEnabled(assetA, false)).to.be.revertedWithCustomError( + oracle, + "Unauthorized", + ); + }); + + it("6b.5 reverts when asset has not been initialized", async () => { + await expect(oracle.setCachingEnabled(assetB, false)).to.be.revertedWithCustomError( + oracle, + "MarketNotInitialized", + ); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 7. updateMinPrice + // ──────────────────────────────────────────────────────────────────── + + describe("7. updateMinPrice", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + }); + + it("7.1 updates minPrice and emits event", async () => { + const newMin = parseUnits("0.85", 18); + const tx = await oracle.updateMinPrice(assetA, newMin); + await expect(tx).to.emit(oracle, "MinPriceUpdated").withArgs(assetA, MIN_PRICE, newMin); + }); + + it("7.2 reverts when caller is unauthorized", async () => { + await expect( + oracle.connect(someone).updateMinPrice(assetA, parseUnits("0.85", 18)), + ).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("7.3 reverts when newMin > currentSpot", async () => { + await expect(oracle.updateMinPrice(assetA, SPOT_PRICE.add(1))).to.be.revertedWithCustomError( + oracle, + "InvalidMinPrice", + ); + }); + + it("7.4 reverts when newMin > maxPrice", async () => { + // Set spot above maxPrice so the spot constraint passes; the maxPrice check is what reverts + await mockOracle.setPrice(assetA, MAX_PRICE.add(parseUnits("0.05", 18))); + await expect(oracle.updateMinPrice(assetA, MAX_PRICE.add(1))).to.be.revertedWithCustomError( + oracle, + "InvalidMinPrice", + ); + }); + + it("7.5 succeeds when newMin == maxPrice == spot (full convergence)", async () => { + // Spot equal to maxPrice so newMin = maxPrice = spot is valid under the relaxed semantics + await mockOracle.setPrice(assetA, MAX_PRICE); + await expect(oracle.updateMinPrice(assetA, MAX_PRICE)).to.not.be.reverted; + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(MAX_PRICE); + expect(state.maxPrice).to.equal(MAX_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 8. updateMaxPrice + // ──────────────────────────────────────────────────────────────────── + + describe("8. updateMaxPrice", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + }); + + it("8.1 updates maxPrice and emits event", async () => { + const tx = await oracle.updateMaxPrice(assetA, parseUnits("1.15", 18)); + await expect(tx).to.emit(oracle, "MaxPriceUpdated").withArgs(assetA, MAX_PRICE, parseUnits("1.15", 18)); + }); + + it("8.2 reverts when caller is unauthorized", async () => { + await expect( + oracle.connect(someone).updateMaxPrice(assetA, parseUnits("1.15", 18)), + ).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("8.3 reverts when newMax < currentSpot", async () => { + await expect(oracle.updateMaxPrice(assetA, SPOT_PRICE.sub(1))).to.be.revertedWithCustomError( + oracle, + "InvalidMaxPrice", + ); + }); + + it("8.4 reverts when newMax < minPrice", async () => { + // Set spot below minPrice so the spot constraint passes; the minPrice check is what reverts + await mockOracle.setPrice(assetA, MIN_PRICE.sub(parseUnits("0.05", 18))); + await expect(oracle.updateMaxPrice(assetA, MIN_PRICE.sub(1))).to.be.revertedWithCustomError( + oracle, + "InvalidMaxPrice", + ); + }); + + it("8.5 succeeds when newMax == minPrice == spot (full convergence)", async () => { + // Spot equal to minPrice so newMax = minPrice = spot is valid under the relaxed semantics + await mockOracle.setPrice(assetA, MIN_PRICE); + await expect(oracle.updateMaxPrice(assetA, MIN_PRICE)).to.not.be.reverted; + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(MIN_PRICE); + expect(state.maxPrice).to.equal(MIN_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 9. exitProtectionMode + // ──────────────────────────────────────────────────────────────────── + + describe("9. exitProtectionMode", () => { + it("9.1 disables protection after governance raises reset threshold", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + const state = await oracle.assetProtectionConfig(assetA); + const range = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const trigger = state.triggerThreshold; + if (newReset.gte(trigger)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, trigger, newReset); + } + + const tx = await oracle.exitProtectionMode(assetA); + await expect(tx).to.emit(oracle, "ProtectionModeExited").withArgs(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("9.2 prices revert to spot after disable", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + + await disableProtection(assetA); + + // Verify lastProtectionTriggeredAt is reset to 0 on disable + const stateDisabled = await oracle.assetProtectionConfig(assetA); + expect(stateDisabled.lastProtectionTriggeredAt).to.equal(0); + expect(stateDisabled.currentlyUsingProtectedPrice).to.equal(false); + + await mockOracle.setPrice(assetA, SPOT_PRICE); + + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(SPOT_PRICE); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("9.3 reverts when caller is unauthorized", async () => { + await initAsset(assetA); + await expect(oracle.connect(someone).exitProtectionMode(assetA)).to.be.revertedWithCustomError( + oracle, + "Unauthorized", + ); + }); + + it("9.4 reverts when protection is not active", async () => { + await initAsset(assetA); + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "ProtectedPriceInactive"); + }); + + it("9.5 reverts when cooldown has not elapsed", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + }); + + it("9.6 reverts when range not converged", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "PriceRangeNotConverged"); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 10. getBoundedCollateralPrice + // ──────────────────────────────────────────────────────────────────── + + describe("10. getBoundedCollateralPrice", () => { + it("10.1 returns spot when not whitelisted", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("10.2 returns spot when whitelisted, no deviation", async () => { + await initAssetWithWindow(assetA); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("10.3 expands window when spot < minPrice", async () => { + await initAssetWithWindow(assetA); + const lowSpot = MIN_PRICE.sub(1); + await mockOracle.setPrice(assetA, lowSpot); + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)) + .to.emit(oracle, "MinPriceUpdated") + .withArgs(assetA, MIN_PRICE, lowSpot); + }); + + it("10.4 expands window when spot > maxPrice", async () => { + await initAssetWithWindow(assetA); + await mockOracle.setPrice(assetA, MAX_PRICE.add(1)); + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)) + .to.emit(oracle, "MaxPriceUpdated") + .withArgs(assetA, MAX_PRICE, MAX_PRICE.add(1)); + }); + + it("10.5 triggers protection on pump and returns minPrice", async () => { + await initAssetWithWindow(assetA); + const upperBound = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE); + await mockOracle.setPrice(assetA, upperBound.add(1)); + + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)).to.emit(oracle, "ProtectionTriggered"); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + }); + + it("10.6 triggers protection on crash and returns spot", async () => { + const localMin = parseUnits("0.995", 18); + const localMax = parseUnits("1.005", 18); + await initAssetWithWindow(assetA, localMin, localMax); + + const lowerBound = localMax.mul(EXP_SCALE.sub(DEFAULT_THRESHOLD)).div(EXP_SCALE); + const crashSpot = lowerBound.sub(1); + await mockOracle.setPrice(assetA, crashSpot); + + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)).to.emit(oracle, "ProtectionTriggered"); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(crashSpot); + }); + + it("10.7 returns spot for uninitialized asset", async () => { + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(SPOT_PRICE); + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("10.8 resolves native market to NATIVE_TOKEN_ADDR", async () => { + await initAssetWithWindow(NATIVE_TOKEN_ADDR); + expect(await oracle.callStatic.getBoundedCollateralPrice(VBNB_ADDRESS)).to.equal(SPOT_PRICE); + }); + + it("10.9 reverts with PriceExceedsUint128", async () => { + await initAsset(assetA); + await mockOracle.setPrice(assetA, BigNumber.from(2).pow(128)); + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)).to.be.revertedWithCustomError( + oracle, + "PriceExceedsUint128", + ); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 11. getBoundedDebtPrice + // ──────────────────────────────────────────────────────────────────── + + describe("11. getBoundedDebtPrice", () => { + it("11.1 returns spot when no deviation", async () => { + await initAssetWithWindow(assetA); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("11.2 returns maxPrice on crash trigger", async () => { + await initAssetWithWindow(assetA); + const lowerBound = MAX_PRICE.mul(EXP_SCALE.sub(DEFAULT_THRESHOLD)).div(EXP_SCALE); + await mockOracle.setPrice(assetA, lowerBound.sub(1)); + await oracle.getBoundedDebtPrice(vTokenA.address); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal(MAX_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 12. getBoundedPrices + // ──────────────────────────────────────────────────────────────────── + + describe("12. getBoundedPrices", () => { + it("12.1 returns (spot, spot) when no deviation", async () => { + await initAssetWithWindow(assetA); + const [c, d] = await oracle.callStatic.getBoundedPrices(vTokenA.address); + expect(c).to.equal(SPOT_PRICE); + expect(d).to.equal(SPOT_PRICE); + }); + + it("12.2 matches individual functions when protected", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + const [c, d] = await oracle.callStatic.getBoundedPrices(vTokenA.address); + expect(c).to.equal(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)); + expect(d).to.equal(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 13. View price functions + // ──────────────────────────────────────────────────────────────────── + + describe("13. view price functions", () => { + it("13.1 returns spot when no deviation", async () => { + await initAssetWithWindow(assetA); + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + expect(await oracle.getBoundedDebtPriceView(vTokenA.address)).to.equal(SPOT_PRICE); + }); + + it("13.2 simulated trigger without state mutation", async () => { + await initAssetWithWindow(assetA); + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + await mockOracle.setPrice(assetA, pumpSpot); + + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(MIN_PRICE); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("13.3 view and non-view return identical prices (pump)", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal( + await oracle.getBoundedCollateralPriceView(vTokenA.address), + ); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal( + await oracle.getBoundedDebtPriceView(vTokenA.address), + ); + }); + + it("13.4 view and non-view return identical prices (crash)", async () => { + const localMin = parseUnits("0.995", 18); + const localMax = parseUnits("1.005", 18); + await initAssetWithWindow(assetA, localMin, localMax); + + const lowerBound = localMax.mul(EXP_SCALE.sub(DEFAULT_THRESHOLD)).div(EXP_SCALE); + const crashSpot = lowerBound.sub(1); + await mockOracle.setPrice(assetA, crashSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal( + await oracle.getBoundedCollateralPriceView(vTokenA.address), + ); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal( + await oracle.getBoundedDebtPriceView(vTokenA.address), + ); + }); + + it("13.5 view and non-view return identical prices (no deviation)", async () => { + await initAssetWithWindow(assetA); + + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal( + await oracle.getBoundedCollateralPriceView(vTokenA.address), + ); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal( + await oracle.getBoundedDebtPriceView(vTokenA.address), + ); + }); + + it("13.6 view and non-view return identical prices (not whitelisted)", async () => { + await initAsset(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal( + await oracle.getBoundedCollateralPriceView(vTokenA.address), + ); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal( + await oracle.getBoundedDebtPriceView(vTokenA.address), + ); + }); + + it("13.7 view and non-view return identical prices (uninitialized asset)", async () => { + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal( + await oracle.getBoundedCollateralPriceView(vTokenA.address), + ); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal( + await oracle.getBoundedDebtPriceView(vTokenA.address), + ); + }); + + it("13.8 getBoundedPrices matches getBoundedPricesView (protection active)", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await mockOracle.setPrice(assetA, SPOT_PRICE); + + const [nonViewC, nonViewD] = await oracle.callStatic.getBoundedPrices(vTokenA.address); + const [viewC, viewD] = await oracle.getBoundedPricesView(vTokenA.address); + expect(nonViewC).to.equal(viewC); + expect(nonViewD).to.equal(viewD); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 14-15. getBoundedPricesView & updateProtectionState + // ──────────────────────────────────────────────────────────────────── + + describe("14. getBoundedPricesView", () => { + it("14.1 matches individual view functions", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + const [c, d] = await oracle.getBoundedPricesView(vTokenA.address); + expect(c).to.equal(await oracle.getBoundedCollateralPriceView(vTokenA.address)); + expect(d).to.equal(await oracle.getBoundedDebtPriceView(vTokenA.address)); + }); + }); + + describe("15. updateProtectionState", () => { + it("15.1 triggers protection for whitelisted asset", async () => { + await initAssetWithWindow(assetA); + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + await mockOracle.setPrice(assetA, pumpSpot); + + await expect(oracle.updateProtectionState(vTokenA.address)).to.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + + it("15.2 is a no-op for non-whitelisted asset", async () => { + await initAssetWithWindow(assetA); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + await expect(oracle.updateProtectionState(vTokenA.address)).to.not.be.reverted; + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 16-18. Simple getters + // ──────────────────────────────────────────────────────────────────── + + describe("16. isBoundedPricingEnabled", () => { + it("16.1 returns true for enabled, false for disabled/uninitialized", async () => { + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(false); + await initAsset(assetA); + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(true); + await oracle.setAssetBoundedPricingEnabled(assetA, false); + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(false); + }); + }); + + describe("17. currentlyUsingProtectedPrice", () => { + it("17.1 returns true when active, false otherwise", async () => { + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + }); + + describe("18. canExitProtection", () => { + it("18.1 returns false before cooldown, true after cooldown + converged range", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + expect(await oracle.canExitProtection(assetA)).to.equal(false); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + expect(await oracle.canExitProtection(assetA)).to.equal(false); // range still wide + + const state = await oracle.assetProtectionConfig(assetA); + const rangeRatio = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const newReset = rangeRatio.add(parseUnits("0.001", 18)); + if (newReset.gte(state.triggerThreshold)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, state.triggerThreshold, newReset); + } + expect(await oracle.canExitProtection(assetA)).to.equal(true); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 19-22. Enumeration, Drift, Config Getter + // ──────────────────────────────────────────────────────────────────── + + describe("19. getInitializedAssets & getAllBoundedPricingEnabledAssets", () => { + it("19.1 tracks initialized and whitelisted assets", async () => { + expect(await oracle.getInitializedAssets()).to.have.length(0); + await initAsset(assetA); + await initAsset(assetB); + expect(await oracle.getInitializedAssets()).to.have.length(2); + expect(await oracle.getAllBoundedPricingEnabledAssets()).to.have.length(2); + + await oracle.setAssetBoundedPricingEnabled(assetB, false); + expect(await oracle.getInitializedAssets()).to.have.length(2); + expect(await oracle.getAllBoundedPricingEnabledAssets()).to.have.length(1); + }); + }); + + describe("20. checkAndGetWindowDrift", () => { + it("20.1 detects drift beyond deadband", async () => { + await initAssetWithWindow(assetA); + const [needsMin] = await oracle.checkAndGetWindowDrift([assetA], [MIN_PRICE.mul(89).div(100)], [MAX_PRICE]); + expect(needsMin[0]).to.equal(true); + + const [noMin] = await oracle.checkAndGetWindowDrift([assetA], [MIN_PRICE], [MAX_PRICE]); + expect(noMin[0]).to.equal(false); + }); + + it("20.2 reverts when array lengths mismatch", async () => { + await expect( + oracle.checkAndGetWindowDrift([assetA], [MIN_PRICE, MIN_PRICE], [MAX_PRICE]), + ).to.be.revertedWithCustomError(oracle, "InvalidArrayLength"); + }); + }); + + describe("21. assetProtectionConfig getter", () => { + it("21.1 returns all struct fields correctly", async () => { + await initAsset(assetA); + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(SPOT_PRICE); + expect(state.maxPrice).to.equal(SPOT_PRICE); + expect(state.currentlyUsingProtectedPrice).to.equal(false); + expect(state.isBoundedPricingEnabled).to.equal(true); + expect(state.cooldownPeriod).to.equal(DEFAULT_COOLDOWN); + expect(state.asset).to.equal(assetA); + expect(state.triggerThreshold).to.equal(DEFAULT_THRESHOLD); + expect(state.resetThreshold).to.equal(DEFAULT_RESET_THRESHOLD); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 22. Transient Cache + // ──────────────────────────────────────────────────────────────────── + + describe("22. transient cache", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + }); + + it("22.1 cache hit, no protection -- view returns cached spot", async () => { + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(SPOT_PRICE); + expect(result.debt).to.equal(SPOT_PRICE); + }); + + it("22.2 cache hit, protection active", async () => { + await triggerPump(assetA, vTokenA); + const state = await oracle.assetProtectionConfig(assetA); + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(MIN_PRICE); + expect(result.debt).to.equal(state.maxPrice); + }); + + it("22.3 protection triggered + window expanded in same call", async () => { + const crashSpot = parseUnits("0.7", 18); + await mockOracle.setPrice(assetA, crashSpot); + + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(crashSpot); + expect(result.debt).to.equal(MAX_PRICE); + + await caller.updateAndGetBothPrices(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + + it("22.4 cache miss -- view fetches fresh", async () => { + const result = await caller.getViewPricesWithoutUpdate(vTokenA.address); + expect(result.collateral).to.equal(SPOT_PRICE); + }); + + it("22.5 cache miss, deviation -- simulated trigger", async () => { + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + await mockOracle.setPrice(assetA, pumpSpot); + + const result = await caller.getViewPricesWithoutUpdate(vTokenA.address); + expect(result.collateral).to.equal(MIN_PRICE); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("22.6 per-asset cache isolation", async () => { + const specialSpot = parseUnits("2", 18); + await mockOracle.setPrice(assetB, specialSpot); + await initAssetWithWindow(assetB, parseUnits("1.95", 18), parseUnits("2.05", 18)); + + const collateralA = await caller.callStatic.updateAndGetCollateralPrice(vTokenA.address); + expect(collateralA).to.equal(SPOT_PRICE); + + const resultB = await caller.getViewPricesWithoutUpdate(vTokenB.address); + expect(resultB.collateral).to.equal(specialSpot); + }); + + it("22.7 non-whitelisted caches (spot, spot)", async () => { + await oracle.setAssetBoundedPricingEnabled(assetA, false); + const specialSpot = parseUnits("1.5", 18); + await mockOracle.setPrice(assetA, specialSpot); + + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(specialSpot); + expect(result.debt).to.equal(specialSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 22b. cachingEnabled gating (DBO-local flag) + // ──────────────────────────────────────────────────────────────────── + + describe("22b. cachingEnabled gating", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + }); + + it("22b.1 cache hit when cachingEnabled is true (default), no protection", async () => { + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(SPOT_PRICE); + expect(result.debt).to.equal(SPOT_PRICE); + }); + + it("22b.2 views recompute from live spot when cachingEnabled is false", async () => { + // Seed the cache with the original spot, then disable caching. + await caller.updateAndGetBothPrices(vTokenA.address); + await oracle.setCachingEnabled(assetA, false); + + // Change the live spot — cache-disabled views must recompute from it. + const newSpot = parseUnits("1.05", 18); + await mockOracle.setPrice(assetA, newSpot); + + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(newSpot); + expect(result.debt).to.equal(newSpot); + }); + + it("22b.3 re-enabling caching restores cache-hit behaviour", async () => { + await oracle.setCachingEnabled(assetA, false); + await oracle.setCachingEnabled(assetA, true); + + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + expect(result.collateral).to.equal(SPOT_PRICE); + expect(result.debt).to.equal(SPOT_PRICE); + }); + + it("22b.4 cachingEnabled is per-asset and does not leak across assets", async () => { + await initAssetWithWindow(assetB); + await oracle.setCachingEnabled(assetA, false); + + const stateA = await oracle.assetProtectionConfig(assetA); + const stateB = await oracle.assetProtectionConfig(assetB); + expect(stateA.cachingEnabled).to.equal(false); + expect(stateB.cachingEnabled).to.equal(true); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 23. Re-trigger After Disable + // ──────────────────────────────────────────────────────────────────── + + describe("23. re-trigger after disable", () => { + it("23.1 non-view re-triggers", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await disableProtection(assetA); + + await mockOracle.setPrice(assetA, parseUnits("1.2", 18)); + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)).to.emit(oracle, "ProtectionTriggered"); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + }); + + it("23.2 view simulates trigger after disable", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await disableProtection(assetA); + + await mockOracle.setPrice(assetA, parseUnits("1.2", 18)); + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(MIN_PRICE); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("23.3 lastProtectionTriggeredAt is fresh", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + const firstTime = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + await disableProtection(assetA); + + await mockOracle.setPrice(assetA, parseUnits("1.2", 18)); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect((await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt).to.be.gt(firstTime); + }); + + it("23.4 repeated trigger -> disable -> trigger cycle", async () => { + await initAssetWithWindow(assetA); + for (let i = 0; i < 3; i += 1) { + await triggerPump(assetA, vTokenA); + await disableProtection(assetA); + await oracle.setThresholds(assetA, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD); + } + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 24. Threshold Effects + // ──────────────────────────────────────────────────────────────────── + + describe("24. threshold effects", () => { + const spot = parseUnits("1.15", 18); + + it("24.1 lowering threshold triggers protection", async () => { + await oracle.setTokenConfig(tokenCfg(assetA, DEFAULT_COOLDOWN, parseUnits("0.3", 18), parseUnits("0.15", 18))); + await oracle.updateMinPrice(assetA, MIN_PRICE); + await oracle.updateMaxPrice(assetA, MAX_PRICE); + await mockOracle.setPrice(assetA, spot); + + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + const state = await oracle.assetProtectionConfig(assetA); + await oracle.setThresholds(assetA, DEFAULT_THRESHOLD, state.resetThreshold); + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)).to.emit(oracle, "ProtectionTriggered"); + }); + + it("24.2 raising threshold prevents protection", async () => { + await initAssetWithWindow(assetA); + await mockOracle.setPrice(assetA, spot); + const state = await oracle.assetProtectionConfig(assetA); + await oracle.setThresholds(assetA, parseUnits("0.3", 18), state.resetThreshold); + + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("24.3 view reflects threshold change immediately", async () => { + await initAssetWithWindow(assetA); + await mockOracle.setPrice(assetA, spot); + + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(MIN_PRICE); + + const state = await oracle.assetProtectionConfig(assetA); + await oracle.setThresholds(assetA, parseUnits("0.3", 18), state.resetThreshold); + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(spot); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 25. No Re-trigger When Active + // ──────────────────────────────────────────────────────────────────── + + describe("25. no re-trigger, window expands", () => { + it("25.1 continued deviation updates timestamp and re-emits event", async () => { + await initAssetWithWindow(assetA); + const pumpSpot = await triggerPump(assetA, vTokenA); + const triggerTime = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + await mockOracle.setPrice(assetA, pumpSpot.add(parseUnits("0.5", 18))); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + expect((await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt).to.be.gte(triggerTime); + }); + + it("25.2 window expands during protection", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + const lowSpot = parseUnits("0.8", 18); + await mockOracle.setPrice(assetA, lowSpot); + await expect(oracle.getBoundedCollateralPrice(vTokenA.address)) + .to.emit(oracle, "MinPriceUpdated") + .withArgs(assetA, MIN_PRICE, lowSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 26. Keeper Updates During Protection + // ──────────────────────────────────────────────────────────────────── + + describe("26. keeper updates during protection", () => { + it("26.1 keeper updates succeed during active protection", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await mockOracle.setPrice(assetA, SPOT_PRICE); + + await expect(oracle.updateMinPrice(assetA, parseUnits("0.85", 18))).to.not.be.reverted; + await expect(oracle.updateMaxPrice(assetA, parseUnits("1.15", 18))).to.not.be.reverted; + }); + + it("26.2 keeper min update affects bounded collateral", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await mockOracle.setPrice(assetA, SPOT_PRICE); + + const newMin = parseUnits("0.85", 18); + await oracle.updateMinPrice(assetA, newMin); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(newMin); + }); + + it("26.3 keeper max update affects bounded debt", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + const originalMax = (await oracle.assetProtectionConfig(assetA)).maxPrice; + await mockOracle.setPrice(assetA, SPOT_PRICE); + + const newMax = originalMax.add(parseUnits("0.1", 18)); + await oracle.updateMaxPrice(assetA, newMax); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal(newMax); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 27. Collateral vs Debt Divergence + // ──────────────────────────────────────────────────────────────────── + + describe("27. collateral vs debt divergence", () => { + beforeEach(async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + }); + + it("27.1 spot between min and max", async () => { + await mockOracle.setPrice(assetA, SPOT_PRICE); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal( + (await oracle.assetProtectionConfig(assetA)).maxPrice, + ); + }); + + it("27.2 spot below min", async () => { + const lowSpot = parseUnits("0.8", 18); + await mockOracle.setPrice(assetA, lowSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(lowSpot); + }); + + it("27.3 spot above max", async () => { + const state = await oracle.assetProtectionConfig(assetA); + const highSpot = state.maxPrice.add(parseUnits("0.5", 18)); + await mockOracle.setPrice(assetA, highSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal(highSpot); + }); + + it("27.4 price normalizes but protection still active -- bounded pricing still applies", async () => { + await mockOracle.setPrice(assetA, SPOT_PRICE); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + const collateral = await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address); + const debt = await oracle.callStatic.getBoundedDebtPrice(vTokenA.address); + const state = await oracle.assetProtectionConfig(assetA); + + // Protection is time-gated, not price-gated + expect(collateral).to.equal(MIN_PRICE); + expect(debt).to.equal(state.maxPrice); + expect(collateral).to.not.equal(SPOT_PRICE); + expect(debt).to.not.equal(SPOT_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 28. Multiple Assets Independence + // ──────────────────────────────────────────────────────────────────── + + describe("28. multiple assets independence", () => { + it("28.1 trigger on one, other unaffected", async () => { + await initAssetWithWindow(assetA); + await initAssetWithWindow(assetB); + + await mockOracle.setPrice(assetA, parseUnits("1.2", 18)); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + expect(await oracle.currentlyUsingProtectedPrice(assetB)).to.equal(false); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenB.address)).to.equal(SPOT_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 29. Whitelist Round-Trip + // ──────────────────────────────────────────────────────────────────── + + describe("29. whitelist round-trip", () => { + it("29.1 disable returns spot, re-enable resets window to current spot", async () => { + await initAssetWithWindow(assetA); + const pumpSpot = parseUnits("1.2", 18); + await mockOracle.setPrice(assetA, pumpSpot); + + await oracle.setAssetBoundedPricingEnabled(assetA, false); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(pumpSpot); + + // Re-enable resets window to current spot (min=max=pumpSpot), no deviation + await oracle.setAssetBoundedPricingEnabled(assetA, true); + expect(await oracle.getBoundedCollateralPriceView(vTokenA.address)).to.equal(pumpSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 30. Sequential Calls in Same Tx + // ──────────────────────────────────────────────────────────────────── + + describe("30. sequential calls in same tx", () => { + it("30.1 two consecutive non-view calls", async () => { + await initAssetWithWindow(assetA); + await mockOracle.setPrice(assetA, parseUnits("1.2", 18)); + + const result = await caller.callStatic.twoConsecutiveNonViewCollateral(vTokenA.address); + expect(result.first).to.equal(MIN_PRICE); + expect(result.second).to.equal(MIN_PRICE); + }); + + it("30.2 updateProtectionState then non-view reads cache", async () => { + await initAssetWithWindow(assetA); + await mockOracle.setPrice(assetA, parseUnits("1.2", 18)); + expect(await caller.callStatic.updateThenNonViewCollateral(vTokenA.address)).to.equal(MIN_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 31. Atomic Expansion + Trigger + // ──────────────────────────────────────────────────────────────────── + + describe("31. atomic expansion + trigger", () => { + it("31.1 spot jumps far above maxPrice", async () => { + await initAssetWithWindow(assetA); + const bigPump = parseUnits("2", 18); + await mockOracle.setPrice(assetA, bigPump); + + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "MaxPriceUpdated").withArgs(assetA, MAX_PRICE, bigPump); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 32. Boundary Precision + // ──────────────────────────────────────────────────────────────────── + + describe("32. boundary precision", () => { + it("32.1 exact boundary does NOT trigger, +1 triggers", async () => { + await initAssetWithWindow(assetA); + const upperBound = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE); + + await mockOracle.setPrice(assetA, upperBound); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + + await mockOracle.setPrice(assetA, upperBound.add(1)); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + }); + + it("32.2 reset threshold strict inequality", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + const state = await oracle.assetProtectionConfig(assetA); + const rangeRatio = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const trigger = rangeRatio.add(parseUnits("0.01", 18)); + + await oracle.setThresholds(assetA, trigger, rangeRatio); + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "PriceRangeNotConverged"); + + if (rangeRatio.add(1).lt(trigger)) { + await oracle.setThresholds(assetA, trigger, rangeRatio.add(1)); + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + } + }); + + it("32.3 deadband strict inequality", async () => { + await initAssetWithWindow(assetA); + const exact = MIN_PRICE.mul(EXP_SCALE.sub(KEEPER_DEADBAND)).div(EXP_SCALE); + + const [noUpdate] = await oracle.checkAndGetWindowDrift([assetA], [exact], [MAX_PRICE]); + expect(noUpdate[0]).to.equal(false); + + const [yesUpdate] = await oracle.checkAndGetWindowDrift([assetA], [exact.sub(1)], [MAX_PRICE]); + expect(yesUpdate[0]).to.equal(true); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 33. Dual-Price API + // ──────────────────────────────────────────────────────────────────── + + describe("33. dual-price API", () => { + it("33.1 updateProtectionState -> getBoundedPricesView returns cached", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await mockOracle.setPrice(assetA, SPOT_PRICE); + + const result = await caller.callStatic.updateAndGetBothPrices(vTokenA.address); + const state = await oracle.assetProtectionConfig(assetA); + expect(result.collateral).to.equal(MIN_PRICE); + expect(result.debt).to.equal(state.maxPrice); + }); + + it("33.2 getBoundedPrices matches individual functions", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await mockOracle.setPrice(assetA, SPOT_PRICE); + + const [c, d] = await oracle.callStatic.getBoundedPrices(vTokenA.address); + expect(c).to.equal(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)); + expect(d).to.equal(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)); + }); + + it("33.3 getBoundedPricesView matches individual views", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await mockOracle.setPrice(assetA, SPOT_PRICE); + + const [c, d] = await oracle.getBoundedPricesView(vTokenA.address); + expect(c).to.equal(await oracle.getBoundedCollateralPriceView(vTokenA.address)); + expect(d).to.equal(await oracle.getBoundedDebtPriceView(vTokenA.address)); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 34. setTokenConfigs (batch) + // ──────────────────────────────────────────────────────────────────── + + describe("34. setTokenConfigs (batch)", () => { + const cfg = (asset: string, enableBoundedPricing = true, enableCaching = true) => ({ + asset, + cooldownPeriod: DEFAULT_COOLDOWN, + triggerThreshold: DEFAULT_THRESHOLD, + resetThreshold: DEFAULT_RESET_THRESHOLD, + enableBoundedPricing, + enableCaching, + }); + + it("34.1 batch-initializes multiple assets", async () => { + const tx = await oracle.setTokenConfigs([cfg(assetA), cfg(assetB)]); + + const stateA = await oracle.assetProtectionConfig(assetA); + expect(stateA.asset).to.equal(assetA); + expect(stateA.isBoundedPricingEnabled).to.equal(true); + + const stateB = await oracle.assetProtectionConfig(assetB); + expect(stateB.asset).to.equal(assetB); + + await expect(tx).to.emit(oracle, "ProtectionInitialized"); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, true); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetB, true); + + expect(await oracle.getInitializedAssets()).to.have.length(2); + }); + + it("34.2 batch with mixed enableBoundedPricing values", async () => { + await oracle.setTokenConfigs([cfg(assetA, true, true), cfg(assetB, false, true)]); + + expect(await oracle.isBoundedPricingEnabled(assetA)).to.equal(true); + expect(await oracle.isBoundedPricingEnabled(assetB)).to.equal(false); + }); + + it("34.3 reverts when caller is unauthorized", async () => { + await expect(oracle.connect(someone).setTokenConfigs([cfg(assetA)])).to.be.revertedWithCustomError( + oracle, + "Unauthorized", + ); + }); + + it("34.4 reverts on empty input", async () => { + await expect(oracle.setTokenConfigs([])).to.be.revertedWithCustomError(oracle, "InvalidArrayLength"); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 35. setTokenConfig with enableBoundedPricing=false + // ──────────────────────────────────────────────────────────────────── + + describe("35. setTokenConfig with enableBoundedPricing=false", () => { + it("35.1 initializes with bounded pricing disabled", async () => { + const tx = await oracle.setTokenConfig( + tokenCfg(assetA, DEFAULT_COOLDOWN, DEFAULT_THRESHOLD, DEFAULT_RESET_THRESHOLD, false, true), + ); + + expect((await oracle.assetProtectionConfig(assetA)).isBoundedPricingEnabled).to.equal(false); + await expect(tx).to.emit(oracle, "BoundedPricingWhitelistUpdated").withArgs(assetA, false); + + // Pump spot — returns spot (not bounded) since disabled + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + await mockOracle.setPrice(assetA, pumpSpot); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(pumpSpot); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 36. Volatile price extends protection period + // ──────────────────────────────────────────────────────────────────── + + describe("36. volatile price extends protection period", () => { + it("36.1 continued deviation extends cooldown, blocks early disable", async () => { + await initAssetWithWindow(assetA); + + const pumpSpot = await triggerPump(assetA, vTokenA); + const firstTriggerTime = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + // Advance half cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // Another deviating price + await mockOracle.setPrice(assetA, pumpSpot.add(parseUnits("0.2", 18))); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(tx).to.emit(oracle, "ProtectionTriggered"); + + const secondTriggerTime = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + expect(secondTriggerTime).to.be.gt(firstTriggerTime); + + // Advance half cooldown (total = cooldown from initial, half from latest) + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // Raise reset threshold + const state = await oracle.assetProtectionConfig(assetA); + const range = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const trigger = state.triggerThreshold; + if (newReset.gte(trigger)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, trigger, newReset); + } + + // Should revert — cooldown restarted + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + + // Advance remaining + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2 + 1]); + await ethers.provider.send("evm_mine", []); + + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + }); + + it("36.2 price normalizes — no event, timestamp unchanged, protection still active", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + const triggerTime = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + await mockOracle.setPrice(assetA, SPOT_PRICE); + const tx = await oracle.getBoundedCollateralPrice(vTokenA.address); + + await expect(tx).to.not.emit(oracle, "ProtectionTriggered"); + expect((await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt).to.equal(triggerTime); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Bounded pricing still applies + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + }); + + it("36.3 full volatile cycle — repeated spikes extend protection, normalization in between, final disable", async () => { + await initAssetWithWindow(assetA); + + // ── Cycle 1: initial trigger ── + const pumpSpot1 = await triggerPump(assetA, vTokenA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + const trigger1 = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + // Price normalizes mid-cycle — protection still active + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 4]); + await ethers.provider.send("evm_mine", []); + await mockOracle.setPrice(assetA, SPOT_PRICE); + const txNorm1 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(txNorm1).to.not.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + + // ── Cycle 2: second spike extends cooldown (keep within window to avoid exceeding MAX_THRESHOLD) ── + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 4]); + await ethers.provider.send("evm_mine", []); + const pumpSpot2 = pumpSpot1.add(parseUnits("0.05", 18)); + await mockOracle.setPrice(assetA, pumpSpot2); + const txSpike2 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(txSpike2).to.emit(oracle, "ProtectionTriggered"); + const trigger2 = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + expect(trigger2).to.be.gt(trigger1); + + // Try disable after original cooldown elapsed but not after second trigger + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2 + 1]); + await ethers.provider.send("evm_mine", []); + + const stateM = await oracle.assetProtectionConfig(assetA); + const rangeM = stateM.maxPrice.sub(stateM.minPrice).mul(EXP_SCALE).div(stateM.minPrice); + const newResetM = rangeM.add(parseUnits("0.001", 18)); + const triggerM = stateM.triggerThreshold; + if (newResetM.gte(triggerM)) { + await oracle.setThresholds(assetA, newResetM.add(parseUnits("0.01", 18)), newResetM); + } else { + await oracle.setThresholds(assetA, triggerM, newResetM); + } + + // Disable fails — cooldown restarted from second spike + await expect(oracle.exitProtectionMode(assetA)).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + + // Price normalizes again — still protected + await mockOracle.setPrice(assetA, SPOT_PRICE); + const txNorm2 = await oracle.getBoundedCollateralPrice(vTokenA.address); + await expect(txNorm2).to.not.emit(oracle, "ProtectionTriggered"); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(MIN_PRICE); + + // ── Final: wait full cooldown from last spike, disable succeeds ── + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN]); + await ethers.provider.send("evm_mine", []); + + await oracle.exitProtectionMode(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + expect((await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt).to.equal(0); + + // Spot prices returned after disable + expect(await oracle.callStatic.getBoundedCollateralPrice(vTokenA.address)).to.equal(SPOT_PRICE); + expect(await oracle.callStatic.getBoundedDebtPrice(vTokenA.address)).to.equal(SPOT_PRICE); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 37. M01: cooldown reset only on genuine window expansion + // ──────────────────────────────────────────────────────────────────── + + describe("37. M01: cooldown reset only on genuine window expansion", () => { + it("37.1 recovery within the existing window does NOT refresh the cooldown — exitProtectionMode stays reachable", async () => { + await initAssetWithWindow(assetA); + + // Mild crash: spot < MAX_PRICE * (1 - threshold) = 1.1 * 0.8 = 0.88, + // and resulting range (~29%) stays under MAX_THRESHOLD = 50% so we can + // bump setThresholds at the end. + const crashSpot = parseUnits("0.85", 18); + await mockOracle.setPrice(assetA, crashSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const stateAfterCrash = await oracle.assetProtectionConfig(assetA); + const t0 = stateAfterCrash.lastProtectionTriggeredAt; + expect(stateAfterCrash.minPrice).to.equal(crashSpot); + expect(stateAfterCrash.currentlyUsingProtectedPrice).to.equal(true); + + // Recovery within the existing window but above the (post-crash) pump threshold + // (1.05 > 0.85 * 1.2 = 1.02). Pre-M01 this would have refreshed the cooldown. + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + await mockOracle.setPrice(assetA, parseUnits("1.05", 18)); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const stateAfterRecovery = await oracle.assetProtectionConfig(assetA); + expect(stateAfterRecovery.lastProtectionTriggeredAt).to.equal(t0); + expect(stateAfterRecovery.minPrice).to.equal(crashSpot); + expect(stateAfterRecovery.maxPrice).to.equal(MAX_PRICE); + + // Finish the original cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2 + 1]); + await ethers.provider.send("evm_mine", []); + + // Bump resetThreshold above the current range (~29.4%) so the convergence check passes + const range = stateAfterRecovery.maxPrice + .sub(stateAfterRecovery.minPrice) + .mul(EXP_SCALE) + .div(stateAfterRecovery.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const newTrigger = newReset.add(parseUnits("0.01", 18)); + await oracle.setThresholds(assetA, newTrigger, newReset); + + await expect(oracle.exitProtectionMode(assetA)).to.not.be.reverted; + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("37.2 a deeper crash (new low) DOES refresh the cooldown", async () => { + await initAssetWithWindow(assetA); + + const firstCrash = parseUnits("0.85", 18); + await mockOracle.setPrice(assetA, firstCrash); + await oracle.getBoundedCollateralPrice(vTokenA.address); + const t0 = (await oracle.assetProtectionConfig(assetA)).lastProtectionTriggeredAt; + + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN / 2]); + await ethers.provider.send("evm_mine", []); + + // New low: minPrice expands → windowExpanded = true → cooldown resets + const deeperCrash = parseUnits("0.8", 18); + await mockOracle.setPrice(assetA, deeperCrash); + await oracle.getBoundedCollateralPrice(vTokenA.address); + + const stateAfter = await oracle.assetProtectionConfig(assetA); + expect(stateAfter.lastProtectionTriggeredAt).to.be.gt(t0); + expect(stateAfter.minPrice).to.equal(deeperCrash); + }); + }); + + // ──────────────────────────────────────────────────────────────────── + // 38. syncPriceBoundsAndProtections (keeper batch) + // ──────────────────────────────────────────────────────────────────── + + describe("38. syncPriceBoundsAndProtections", () => { + // KeeperAction enum: 0 = SetMinPrice, 1 = SetMaxPrice, 2 = ExitProtectionMode + const SetMinPrice = 0; + const SetMaxPrice = 1; + const ExitProtectionMode = 2; + + it("38.1 reverts when caller is unauthorized", async () => { + await initAssetWithWindow(assetA); + await expect( + oracle + .connect(someone) + .syncPriceBoundsAndProtections([{ asset: assetA, action: SetMinPrice, value: parseUnits("0.85", 18) }]), + ).to.be.revertedWithCustomError(oracle, "Unauthorized"); + }); + + it("38.2 succeeds with empty array (no-op)", async () => { + await expect(oracle.syncPriceBoundsAndProtections([])).to.not.be.reverted; + }); + + it("38.3 single SetMinPrice item updates the asset and emits MinPriceUpdated", async () => { + await initAssetWithWindow(assetA); + const newMin = parseUnits("0.85", 18); + const tx = await oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: SetMinPrice, value: newMin }]); + await expect(tx).to.emit(oracle, "MinPriceUpdated").withArgs(assetA, MIN_PRICE, newMin); + }); + + it("38.4 single SetMaxPrice item updates the asset and emits MaxPriceUpdated", async () => { + await initAssetWithWindow(assetA); + const newMax = parseUnits("1.15", 18); + const tx = await oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: SetMaxPrice, value: newMax }]); + await expect(tx).to.emit(oracle, "MaxPriceUpdated").withArgs(assetA, MAX_PRICE, newMax); + }); + + it("38.5 single ExitProtectionMode item clears protection", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + + // Cooldown elapses + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + // Raise reset threshold so the range check passes + const state = await oracle.assetProtectionConfig(assetA); + const range = state.maxPrice.sub(state.minPrice).mul(EXP_SCALE).div(state.minPrice); + const newReset = range.add(parseUnits("0.001", 18)); + const trigger = state.triggerThreshold; + if (newReset.gte(trigger)) { + await oracle.setThresholds(assetA, newReset.add(parseUnits("0.01", 18)), newReset); + } else { + await oracle.setThresholds(assetA, trigger, newReset); + } + + const tx = await oracle.syncPriceBoundsAndProtections([ + { asset: assetA, action: ExitProtectionMode, value: 0 }, + ]); + await expect(tx).to.emit(oracle, "ProtectionModeExited").withArgs(assetA); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(false); + }); + + it("38.6 mixed batch (SetMin, SetMax, Exit) converges and exits in one tx", async () => { + await initAssetWithWindow(assetA); + + // Trigger via pump + const pumpSpot = MIN_PRICE.mul(EXP_SCALE.add(DEFAULT_THRESHOLD)).div(EXP_SCALE).add(1); + await mockOracle.setPrice(assetA, pumpSpot); + await oracle.getBoundedCollateralPrice(vTokenA.address); + expect(await oracle.currentlyUsingProtectedPrice(assetA)).to.equal(true); + + // Spot stabilises somewhere inside the post-trigger window + const stableSpot = MIN_PRICE.add(pumpSpot).div(2); + await mockOracle.setPrice(assetA, stableSpot); + + // Wait out cooldown + await ethers.provider.send("evm_increaseTime", [DEFAULT_COOLDOWN + 1]); + await ethers.provider.send("evm_mine", []); + + const tx = await oracle.syncPriceBoundsAndProtections([ + { asset: assetA, action: SetMinPrice, value: stableSpot }, + { asset: assetA, action: SetMaxPrice, value: stableSpot }, + { asset: assetA, action: ExitProtectionMode, value: 0 }, + ]); + + await expect(tx) + .to.emit(oracle, "MinPriceUpdated") + .and.to.emit(oracle, "MaxPriceUpdated") + .and.to.emit(oracle, "ProtectionModeExited") + .withArgs(assetA); + + const state = await oracle.assetProtectionConfig(assetA); + expect(state.minPrice).to.equal(stableSpot); + expect(state.maxPrice).to.equal(stableSpot); + expect(state.currentlyUsingProtectedPrice).to.equal(false); + }); + + it("38.7 revert in any item rolls back the whole batch", async () => { + await initAssetWithWindow(assetA); + const stateBefore = await oracle.assetProtectionConfig(assetA); + const aboveSpot = SPOT_PRICE.add(parseUnits("0.5", 18)); + + await expect( + oracle.syncPriceBoundsAndProtections([ + { asset: assetA, action: SetMinPrice, value: parseUnits("0.85", 18) }, + { asset: assetA, action: SetMinPrice, value: aboveSpot }, + ]), + ).to.be.revertedWithCustomError(oracle, "InvalidMinPrice"); + + const stateAfter = await oracle.assetProtectionConfig(assetA); + expect(stateAfter.minPrice).to.equal(stateBefore.minPrice); + }); + + it("38.8 reverts with PriceExceedsUint128 when value overflows uint128", async () => { + await initAssetWithWindow(assetA); + const overflow = BigNumber.from(2).pow(128); + await expect( + oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: SetMinPrice, value: overflow }]), + ).to.be.revertedWithCustomError(oracle, "PriceExceedsUint128"); + }); + + it("38.9 ExitProtectionMode before cooldown still reverts with CooldownNotElapsed", async () => { + await initAssetWithWindow(assetA); + await triggerPump(assetA, vTokenA); + await expect( + oracle.syncPriceBoundsAndProtections([{ asset: assetA, action: ExitProtectionMode, value: 0 }]), + ).to.be.revertedWithCustomError(oracle, "CooldownNotElapsed"); + }); + }); + }); +}