├── .github └── workflows │ ├── publish-beta-release.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── abi ├── EVM2EVMOffRamp.json ├── EVM2EVMOnRamp.json ├── LinkToken.json └── Router.json ├── api_reference ├── index.mdx ├── javascript │ ├── CCIPLocalSimulatorFork.mdx │ └── index.mdx └── solidity │ ├── ccip │ ├── BurnMintERC677Helper.mdx │ ├── CCIPLocalSimulator.mdx │ ├── CCIPLocalSimulatorFork.mdx │ ├── Register.mdx │ └── index.mdx │ ├── data-feeds │ ├── MockOffchainAggregator.mdx │ ├── MockV3Aggregator.mdx │ ├── index.mdx │ └── interfaces │ │ ├── AggregatorInterface.mdx │ │ ├── AggregatorV2V3Interface.mdx │ │ ├── AggregatorV3Interface.mdx │ │ └── index.mdx │ ├── data-streams │ ├── DataStreamsLocalSimulator.mdx │ ├── DataStreamsLocalSimulatorFork.mdx │ ├── MockFeeManager.mdx │ ├── MockReportGenerator.mdx │ ├── MockRewardManager.mdx │ ├── MockVerifier.mdx │ ├── MockVerifierProxy.mdx │ ├── Register.mdx │ ├── ReportVersions.mdx │ ├── index.mdx │ └── interfaces │ │ ├── IRewardManager.mdx │ │ ├── IVerifier.mdx │ │ ├── IVerifierFeeManager.mdx │ │ └── index.mdx │ ├── index.mdx │ └── shared │ ├── LinkToken.mdx │ ├── WETH9.mdx │ └── index.mdx ├── assets └── thumbnail.png ├── foundry.toml ├── hardhat.config.ts ├── helper_doc ├── generate-index-files.ts └── generate-jsdoc.ts ├── package-lock.json ├── package.json ├── scripts ├── CCIPLocalSimulatorFork.js ├── data-streams │ ├── DataStreamsLocalSimulatorFork.js │ ├── MockReportGenerator.js │ └── ReportVersions.js └── examples │ ├── DataStreamsConsumerFork.ts │ ├── UnsafeTokenAndDataTransfer.ts │ └── UnsafeTokenAndDataTransferFork.ts ├── src ├── ccip │ ├── BurnMintERC677Helper.sol │ ├── CCIPLocalSimulator.sol │ ├── CCIPLocalSimulatorFork.sol │ └── Register.sol ├── data-feeds │ ├── MockOffchainAggregator.sol │ ├── MockV3Aggregator.sol │ └── interfaces │ │ ├── AggregatorInterface.sol │ │ ├── AggregatorV2V3Interface.sol │ │ └── AggregatorV3Interface.sol ├── data-streams │ ├── DataStreamsLocalSimulator.sol │ ├── DataStreamsLocalSimulatorFork.sol │ ├── MockFeeManager.sol │ ├── MockReportGenerator.sol │ ├── MockRewardManager.sol │ ├── MockVerifier.sol │ ├── MockVerifierProxy.sol │ ├── Register.sol │ ├── ReportVersions.sol │ └── interfaces │ │ ├── IRewardManager.sol │ │ ├── IVerifier.sol │ │ └── IVerifierFeeManager.sol ├── shared │ ├── LinkToken.sol │ └── WETH9.sol └── test │ ├── ccip │ ├── BasicTokenSender.sol │ ├── CCIPReceiver_Unsafe.sol │ ├── CCIPSender_Unsafe.sol │ ├── Ping.sol │ ├── Pong.sol │ ├── ProgrammableDefensiveTokenTransfers.sol │ ├── ProgrammableTokenTransfers.sol │ └── TokenTransferor.sol │ ├── data-feeds │ └── BasicDataConsumerV3.sol │ └── data-streams │ ├── ChainlinkDataStreamProvider.sol │ ├── ClientReportsVerifier.sol │ └── ERC7412Compatible.sol ├── test ├── e2e │ ├── ccip │ │ ├── CCIPv1_5ForkBurnMintPoolFork.t.sol │ │ ├── CCIPv1_5LockReleasePoolFork.t.sol │ │ ├── PingPongFork.t.sol │ │ └── TokenTransferorFork.t.sol │ ├── data-feeds │ │ └── BasicDataConsumerV3.t.sol │ └── data-streams │ │ └── DataStreamsConsumerFork.t.sol ├── smoke │ ├── ccip │ │ ├── PayWithNative.t.sol │ │ ├── PingPong.t.sol │ │ ├── ProgrammableDefensiveTokenTransfers.t.sol │ │ ├── ProgrammableTokenTransfers.t.sol │ │ ├── TokenTransferor.t.sol │ │ ├── UnsafeTokenAndDataTransfer.spec.ts │ │ └── UnsafeTokenAndDataTransfer.t.sol │ ├── data-feeds │ │ ├── BasicDataConsumerV3.spec.ts │ │ └── BasicDataConsumerV3.t.sol │ └── data-streams │ │ ├── ChainlinkDataStreamProvider.t.sol │ │ ├── ClientReportsVerifier.spec.ts │ │ ├── ClientReportsVerifier.t.sol │ │ └── ERC7412Compatible.t.sol └── unit │ ├── ccip │ └── CCIPLocalSimulatorUnit.t.sol │ └── data-feeds │ └── DataFeedsUnit.t.sol └── tsconfig.json /.github/workflows/publish-beta-release.yml: -------------------------------------------------------------------------------- 1 | name: Manual NPM Publish (Beta Release) 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | environment: publish 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: "20.x" 19 | registry-url: "https://registry.npmjs.org" 20 | always-auth: true 21 | 22 | - name: Setup npm authentication 23 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc 24 | 25 | - name: Install dependencies 26 | run: npm ci 27 | 28 | - name: Publish 29 | run: npm publish --tag beta 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Manual NPM Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | environment: publish 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: "20.x" 19 | registry-url: "https://registry.npmjs.org" 20 | always-auth: true 21 | 22 | - name: Setup npm authentication 23 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc 24 | 25 | - name: Install dependencies 26 | run: npm ci 27 | 28 | - name: Publish 29 | run: npm publish 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .env.enc 4 | .DS_Store 5 | 6 | # Foundry files 7 | cache_forge/ 8 | out/ 9 | 10 | # Hardhat files 11 | /cache 12 | /artifacts 13 | 14 | # TypeChain files 15 | /typechain 16 | /typechain-types 17 | 18 | # solidity-coverage files 19 | /coverage 20 | /coverage.json 21 | 22 | # Ignores development broadcast logs 23 | !/broadcast 24 | /broadcast/*/31337/ 25 | /broadcast/**/dry-run/ 26 | 27 | # Docs 28 | docs/ 29 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/ccip"] 5 | path = lib/ccip 6 | url = https://github.com/smartcontractkit/ccip 7 | [submodule "lib/chainlink-brownie-contracts"] 8 | path = lib/chainlink-brownie-contracts 9 | url = https://github.com/smartcontractkit/chainlink-brownie-contracts 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "avoid", 11 | "proseWrap": "always" 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 SmartContract ChainLink Limited SEZC 2 | 3 | Portions of this software are licensed as follows: 4 | 5 | The MIT License (MIT) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | 26 | *All content residing under (1) “src/ccip/BurnMintERC677Helper.sol”; (2) 27 | “src/ccip/CCIPLocalSimulator.sol”; (3) “src/ccip/CCIPLocalSimulatorFork.sol”; (4) "src/ccip/Register.sol" are licensed 28 | under “Business Source License 1.1” with a Change Date of May 23, 2027 and 29 | Change License to “MIT License” 30 | 31 | * Content outside of the above mentioned directories or restrictions 32 | above is available under the "MIT" license as defined above. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Chainlink Local 2 | 3 | Chainlink Local is an installable dependency. It provides a tool (the Chainlink Local Simulator) that developers import into their Foundry or Hardhat or Remix projects. This tool runs [Chainlink CCIP](https://docs.chain.link/ccip) locally which means developers can rapidly explore, prototype and iterate CCIP dApps off-chain in a local environment, and move to testnet only when they're ready to test in a live environment. 4 | 5 | The package exposes a set of smart contracts and scripts with which you build, deploy and execute CCIP token transfers and arbitrary messages on a local Remix, Hardhat or Anvil (Foundry) development node. Chainlink Local also supports forked nodes. 6 | 7 | User Contracts tested with Chainlink Local can be deployed to test networks without any modifications (assuming network specific contract addresses such as Router contracts and LINK token addresses are passed in via a constructor). 8 | 9 | To view more detailed documentation and more examples, visit the [Chainlink Local Documentation](https://docs.chain.link/chainlink-local). 10 | 11 |

12 | 13 | Watch the demo on YouTube 14 | 15 |

16 | 17 | ### Installation 18 | 19 | Install the package by running: 20 | 21 | #### Foundry (git) 22 | 23 | ``` 24 | forge install smartcontractkit/chainlink-local 25 | ``` 26 | 27 | and then set remappings to: `@chainlink/local/=lib/chainlink-local/` in either `remappings.txt` or `foundry.toml` file 28 | 29 | #### Foundry (soldeer) 30 | 31 | ``` 32 | forge soldeer install chainlink-local~v0.2.4-beta https://github.com/smartcontractkit/chainlink-local.git 33 | ``` 34 | Replace `v0.2.4-beta` with your desired version number. 35 | 36 | #### Hardhat (npm) 37 | 38 | ``` 39 | npm install @chainlink/local 40 | ``` 41 | 42 | #### Remix IDE 43 | 44 | ```solidity 45 | import "https://github.com/smartcontractkit/chainlink-local/blob/main/src/ccip/CCIPLocalSimulator.sol"; 46 | ``` 47 | 48 | Once you have installed CCIP Local, you are now ready to start using it with your project. 49 | 50 | ### Usage 51 | 52 | Import `CCIPLocalSimulator.sol` inside your tests or scripts, for example: 53 | 54 | ```solidity 55 | // test/demo.t.sol 56 | 57 | pragma solidity ^0.8.19; 58 | 59 | import {Test, console2} from "forge-std/Test.sol"; 60 | import {IRouterClient, WETH9, LinkToken, BurnMintERC677Helper} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 61 | import {CCIPLocalSimulator} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 62 | 63 | contract Demo is Test { 64 | CCIPLocalSimulator public ccipLocalSimulator; 65 | 66 | function setUp() public { 67 | ccipLocalSimulator = new CCIPLocalSimulator(); 68 | 69 | ( 70 | uint64 chainSelector, 71 | IRouterClient sourceRouter, 72 | IRouterClient destinationRouter, 73 | WETH9 wrappedNative, 74 | LinkToken linkToken, 75 | BurnMintERC677Helper ccipBnM, 76 | BurnMintERC677Helper ccipLnM 77 | ) = ccipLocalSimulator.configuration(); 78 | 79 | 80 | ccipLocalSimulator.requestLinkFromFaucet(receiver, amount); 81 | } 82 | 83 | } 84 | ``` 85 | 86 | ### Learn more 87 | 88 | To view detailed documentation and more examples, visit the [Chainlink Local Documentation](https://docs.chain.link/chainlink-local). 89 | 90 | > **Note** 91 | > 92 | > _This tutorial represents an educational example to use a Chainlink system, product, or service and is provided to demonstrate how to interact with Chainlink’s systems, products, and services to integrate them into your own. This template is provided “AS IS” and “AS AVAILABLE” without warranties of any kind, it has not been audited, and it may be missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the code in this example in a production environment without completing your own audits and application of best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to errors in code._ 93 | -------------------------------------------------------------------------------- /api_reference/index.mdx: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | - [javascript](javascript/index.mdx) 4 | - [solidity](solidity/index.mdx) 5 | -------------------------------------------------------------------------------- /api_reference/javascript/CCIPLocalSimulatorFork.mdx: -------------------------------------------------------------------------------- 1 | ## Functions 2 | 3 |
4 |
5 | 6 | requestLinkFromTheFaucet(linkAddress, to, amount) 7 | {' '} 8 | ⇒ Promise.<string> 9 |
10 |
11 |

Requests LINK tokens from the faucet and returns the transaction hash

12 |
13 |
14 | getEvm2EvmMessage(receipt) ⇒{' '} 15 | 16 | Evm2EvmMessage 17 | {' '} 18 | | null 19 |
20 |
21 |

22 | Parses a transaction receipt to extract the sent message Scans through 23 | transaction logs to find a CCIPSendRequested event and then 24 | decodes it to an object 25 |

26 |
27 |
28 | routeMessage(routerAddress, evm2EvmMessage) ⇒{' '} 29 | Promise.<void> 30 |
31 |
32 |

33 | Routes the sent message from the source network on the destination 34 | (current) network 35 |

36 |
37 |
38 | 39 | ## Typedefs 40 | 41 |
42 |
43 | Evm2EvmMessage : Object 44 |
45 |
46 |
47 | 48 | 49 | 50 | ## requestLinkFromTheFaucet(linkAddress, to, amount) ⇒ Promise.<string> 51 | 52 | Requests LINK tokens from the faucet and returns the transaction hash 53 | 54 | **Kind**: global function 55 | **Returns**: Promise.<string> - Promise resolving to the 56 | transaction hash of the fund transfer 57 | 58 | | Param | Type | Description | 59 | | ----------- | ------------------- | ------------------------------------------------------- | 60 | | linkAddress | string | The address of the LINK contract on the current network | 61 | | to | string | The address to send LINK to | 62 | | amount | bigint | The amount of LINK to request | 63 | 64 | 65 | 66 | ## getEvm2EvmMessage(receipt) ⇒ [Evm2EvmMessage](#Evm2EvmMessage) \| null 67 | 68 | Parses a transaction receipt to extract the sent message Scans through 69 | transaction logs to find a `CCIPSendRequested` event and then decodes it to an 70 | object 71 | 72 | **Kind**: global function 73 | **Returns**: [Evm2EvmMessage](#Evm2EvmMessage) \| 74 | null - Returns either the sent message or null if provided receipt 75 | does not contain `CCIPSendRequested` log 76 | 77 | | Param | Type | Description | 78 | | ------- | ------------------- | ------------------------------------------------ | 79 | | receipt | object | The transaction receipt from the `ccipSend` call | 80 | 81 | 82 | 83 | ## routeMessage(routerAddress, evm2EvmMessage) ⇒ Promise.<void> 84 | 85 | Routes the sent message from the source network on the destination (current) 86 | network 87 | 88 | **Kind**: global function 89 | **Returns**: Promise.<void> - Either resolves with no value 90 | if the message is successfully routed, or reverts 91 | **Throws**: 92 | 93 | - Error Fails if no off-ramp matches the message's source chain 94 | selector or if calling `router.getOffRamps()` 95 | 96 | | Param | Type | Description | 97 | | -------------- | ---------------------------------------------- | --------------------------------- | 98 | | routerAddress | string | Address of the destination Router | 99 | | evm2EvmMessage | [Evm2EvmMessage](#Evm2EvmMessage) | Sent cross-chain message | 100 | 101 | 102 | 103 | ## Evm2EvmMessage : Object 104 | 105 | **Kind**: global typedef 106 | **Properties** 107 | 108 | | Name | Type | 109 | | ------------------- | ----------------------------------------------------------- | 110 | | sourceChainSelector | bigint | 111 | | sender | string | 112 | | receiver | string | 113 | | sequenceNumber | bigint | 114 | | gasLimit | bigint | 115 | | strict | boolean | 116 | | nonce | bigint | 117 | | feeToken | string | 118 | | feeTokenAmount | bigint | 119 | | data | string | 120 | | tokenAmounts | Array.<\{token: string, amount: bigint}> | 121 | | sourceTokenData | Array.<string> | 122 | | messageId | string | 123 | -------------------------------------------------------------------------------- /api_reference/javascript/index.mdx: -------------------------------------------------------------------------------- 1 | # Javascript API Reference 2 | 3 | - [CCIPLocalSimulatorFork](CCIPLocalSimulatorFork.mdx) 4 | -------------------------------------------------------------------------------- /api_reference/solidity/ccip/BurnMintERC677Helper.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## BurnMintERC677Helper 4 | 5 | This contract extends the functionality of the BurnMintERC677 token contract to 6 | include a `drip` function that mints one full token to a specified address. 7 | 8 | _Inherits from the BurnMintERC677 contract and sets the token name, symbol, 9 | decimals, and initial supply in the constructor._ 10 | 11 | ### constructor 12 | 13 | ```solidity 14 | constructor(string name, string symbol) public 15 | ``` 16 | 17 | Constructor to initialize the BurnMintERC677Helper contract with a name and 18 | symbol. 19 | 20 | _Calls the parent constructor of BurnMintERC677 with fixed decimals (18) and 21 | initial supply (0)._ 22 | 23 | #### Parameters 24 | 25 | | Name | Type | Description | 26 | | ------ | ------ | -------------------------- | 27 | | name | string | - The name of the token. | 28 | | symbol | string | - The symbol of the token. | 29 | 30 | ### drip 31 | 32 | ```solidity 33 | function drip(address to) external 34 | ``` 35 | 36 | Mints one full token (1e18) to the specified address. 37 | 38 | _Calls the internal `_mint` function from the BurnMintERC677 contract._ 39 | 40 | #### Parameters 41 | 42 | | Name | Type | Description | 43 | | ---- | ------- | ------------------------------------------ | 44 | | to | address | - The address to receive the minted token. | 45 | -------------------------------------------------------------------------------- /api_reference/solidity/ccip/Register.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## Register 4 | 5 | This contract allows storing and retrieving network details for various chains. 6 | 7 | _Stores network details in a mapping based on chain IDs._ 8 | 9 | ### NetworkDetails 10 | 11 | ```solidity 12 | struct NetworkDetails { 13 | uint64 chainSelector; 14 | address routerAddress; 15 | address linkAddress; 16 | address wrappedNativeAddress; 17 | address ccipBnMAddress; 18 | address ccipLnMAddress; 19 | address rmnProxyAddress; 20 | address registryModuleOwnerCustomAddress; 21 | address tokenAdminRegistryAddress; 22 | } 23 | ``` 24 | 25 | ### s_networkDetails 26 | 27 | ```solidity 28 | mapping(uint256 => struct Register.NetworkDetails) s_networkDetails 29 | ``` 30 | 31 | Mapping to store network details based on chain ID. 32 | 33 | ### constructor 34 | 35 | ```solidity 36 | constructor() public 37 | ``` 38 | 39 | Constructor to initialize the network details for various chains. 40 | 41 | ### getNetworkDetails 42 | 43 | ```solidity 44 | function getNetworkDetails(uint256 chainId) external view returns (struct Register.NetworkDetails networkDetails) 45 | ``` 46 | 47 | Retrieves network details for a given chain ID. 48 | 49 | #### Parameters 50 | 51 | | Name | Type | Description | 52 | | ------- | ------- | --------------------------------------------- | 53 | | chainId | uint256 | - The ID of the chain to get the details for. | 54 | 55 | #### Return Values 56 | 57 | | Name | Type | Description | 58 | | -------------- | ------------------------------ | ------------------------------------------------- | 59 | | networkDetails | struct Register.NetworkDetails | - The network details for the specified chain ID. | 60 | 61 | ### setNetworkDetails 62 | 63 | ```solidity 64 | function setNetworkDetails(uint256 chainId, struct Register.NetworkDetails networkDetails) external 65 | ``` 66 | 67 | Sets the network details for a given chain ID. 68 | 69 | #### Parameters 70 | 71 | | Name | Type | Description | 72 | | -------------- | ------------------------------ | -------------------------------------------------------- | 73 | | chainId | uint256 | - The ID of the chain to set the details for. | 74 | | networkDetails | struct Register.NetworkDetails | - The network details to set for the specified chain ID. | 75 | -------------------------------------------------------------------------------- /api_reference/solidity/ccip/index.mdx: -------------------------------------------------------------------------------- 1 | # Ccip API Reference 2 | 3 | - [BurnMintERC677Helper](BurnMintERC677Helper.mdx) 4 | - [CCIPLocalSimulator](CCIPLocalSimulator.mdx) 5 | - [CCIPLocalSimulatorFork](CCIPLocalSimulatorFork.mdx) 6 | - [Register](Register.mdx) 7 | -------------------------------------------------------------------------------- /api_reference/solidity/data-feeds/MockOffchainAggregator.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## MockOffchainAggregator 4 | 5 | This contract is a mock implementation of an offchain aggregator for testing 6 | purposes. 7 | 8 | _This contract simulates the behavior of an offchain aggregator and allows for 9 | updating answers and round data._ 10 | 11 | ### decimals 12 | 13 | ```solidity 14 | uint8 decimals 15 | ``` 16 | 17 | The number of decimals used by the aggregator. 18 | 19 | ### latestAnswer 20 | 21 | ```solidity 22 | int256 latestAnswer 23 | ``` 24 | 25 | The latest answer reported by the aggregator. 26 | 27 | ### latestTimestamp 28 | 29 | ```solidity 30 | uint256 latestTimestamp 31 | ``` 32 | 33 | The timestamp of the latest answer. 34 | 35 | ### latestRound 36 | 37 | ```solidity 38 | uint256 latestRound 39 | ``` 40 | 41 | The latest round ID. 42 | 43 | ### minAnswer 44 | 45 | ```solidity 46 | int192 minAnswer 47 | ``` 48 | 49 | The minimum answer the aggregator is allowed to report. 50 | 51 | ### maxAnswer 52 | 53 | ```solidity 54 | int192 maxAnswer 55 | ``` 56 | 57 | The maximum answer the aggregator is allowed to report. 58 | 59 | ### getAnswer 60 | 61 | ```solidity 62 | mapping(uint256 => int256) getAnswer 63 | ``` 64 | 65 | Mapping to get the answer for a specific round ID. 66 | 67 | ### getTimestamp 68 | 69 | ```solidity 70 | mapping(uint256 => uint256) getTimestamp 71 | ``` 72 | 73 | Mapping to get the timestamp for a specific round ID. 74 | 75 | ### constructor 76 | 77 | ```solidity 78 | constructor(uint8 _decimals, int256 _initialAnswer) public 79 | ``` 80 | 81 | Constructor to initialize the MockOffchainAggregator contract with initial 82 | parameters. 83 | 84 | #### Parameters 85 | 86 | | Name | Type | Description | 87 | | --------------- | ------ | ------------------------------------------------------ | 88 | | \_decimals | uint8 | - The number of decimals for the aggregator. | 89 | | \_initialAnswer | int256 | - The initial answer to be set in the mock aggregator. | 90 | 91 | ### updateAnswer 92 | 93 | ```solidity 94 | function updateAnswer(int256 _answer) public 95 | ``` 96 | 97 | Updates the answer in the mock aggregator. 98 | 99 | #### Parameters 100 | 101 | | Name | Type | Description | 102 | | -------- | ------ | --------------------------- | 103 | | \_answer | int256 | - The new answer to be set. | 104 | 105 | ### updateRoundData 106 | 107 | ```solidity 108 | function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public 109 | ``` 110 | 111 | Updates the round data in the mock aggregator. 112 | 113 | #### Parameters 114 | 115 | | Name | Type | Description | 116 | | ----------- | ------- | --------------------------------------- | 117 | | \_roundId | uint80 | - The round ID to be updated. | 118 | | \_answer | int256 | - The new answer to be set. | 119 | | \_timestamp | uint256 | - The timestamp to be set. | 120 | | \_startedAt | uint256 | - The timestamp when the round started. | 121 | 122 | ### getRoundData 123 | 124 | ```solidity 125 | function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 126 | ``` 127 | 128 | Gets the round data for a specific round ID. 129 | 130 | #### Parameters 131 | 132 | | Name | Type | Description | 133 | | --------- | ------ | ----------------------------------- | 134 | | \_roundId | uint80 | - The round ID to get the data for. | 135 | 136 | #### Return Values 137 | 138 | | Name | Type | Description | 139 | | --------------- | ------- | ------------------------------------------------ | 140 | | roundId | uint80 | - The round ID. | 141 | | answer | int256 | - The answer for the round. | 142 | | startedAt | uint256 | - The timestamp when the round started. | 143 | | updatedAt | uint256 | - The timestamp when the round was updated. | 144 | | answeredInRound | uint80 | - The round ID in which the answer was computed. | 145 | 146 | ### latestRoundData 147 | 148 | ```solidity 149 | function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 150 | ``` 151 | 152 | Gets the latest round data. 153 | 154 | #### Return Values 155 | 156 | | Name | Type | Description | 157 | | --------------- | ------- | ------------------------------------------------------- | 158 | | roundId | uint80 | - The latest round ID. | 159 | | answer | int256 | - The latest answer. | 160 | | startedAt | uint256 | - The timestamp when the latest round started. | 161 | | updatedAt | uint256 | - The timestamp when the latest round was updated. | 162 | | answeredInRound | uint80 | - The round ID in which the latest answer was computed. | 163 | 164 | ### updateMinAndMaxAnswers 165 | 166 | ```solidity 167 | function updateMinAndMaxAnswers(int192 _minAnswer, int192 _maxAnswer) external 168 | ``` 169 | 170 | Updates the minimum and maximum answers the aggregator can report. 171 | 172 | #### Parameters 173 | 174 | | Name | Type | Description | 175 | | ----------- | ------ | ------------------------- | 176 | | \_minAnswer | int192 | - The new minimum answer. | 177 | | \_maxAnswer | int192 | - The new maximum answer. | 178 | -------------------------------------------------------------------------------- /api_reference/solidity/data-feeds/index.mdx: -------------------------------------------------------------------------------- 1 | # Data-feeds API Reference 2 | 3 | - [MockOffchainAggregator](MockOffchainAggregator.mdx) 4 | - [MockV3Aggregator](MockV3Aggregator.mdx) 5 | - [interfaces](interfaces/index.mdx) 6 | -------------------------------------------------------------------------------- /api_reference/solidity/data-feeds/interfaces/AggregatorInterface.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## AggregatorInterface 4 | 5 | Interface for accessing data from an aggregator contract. 6 | 7 | _Provides methods to get the latest data and historical data for specific 8 | rounds._ 9 | 10 | ### latestAnswer 11 | 12 | ```solidity 13 | function latestAnswer() external view returns (int256) 14 | ``` 15 | 16 | Gets the latest answer from the aggregator. 17 | 18 | #### Return Values 19 | 20 | | Name | Type | Description | 21 | | ---- | ------ | --------------------------- | 22 | | [0] | int256 | int256 - The latest answer. | 23 | 24 | ### latestTimestamp 25 | 26 | ```solidity 27 | function latestTimestamp() external view returns (uint256) 28 | ``` 29 | 30 | Gets the timestamp of the latest answer from the aggregator. 31 | 32 | #### Return Values 33 | 34 | | Name | Type | Description | 35 | | ---- | ------- | --------------------------------------------- | 36 | | [0] | uint256 | uint256 - The timestamp of the latest answer. | 37 | 38 | ### latestRound 39 | 40 | ```solidity 41 | function latestRound() external view returns (uint256) 42 | ``` 43 | 44 | Gets the latest round ID from the aggregator. 45 | 46 | #### Return Values 47 | 48 | | Name | Type | Description | 49 | | ---- | ------- | ------------------------------ | 50 | | [0] | uint256 | uint256 - The latest round ID. | 51 | 52 | ### getAnswer 53 | 54 | ```solidity 55 | function getAnswer(uint256 roundId) external view returns (int256) 56 | ``` 57 | 58 | Gets the answer for a specific round ID. 59 | 60 | #### Parameters 61 | 62 | | Name | Type | Description | 63 | | ------- | ------- | ------------------------------------- | 64 | | roundId | uint256 | - The round ID to get the answer for. | 65 | 66 | #### Return Values 67 | 68 | | Name | Type | Description | 69 | | ---- | ------ | ------------------------------------------- | 70 | | [0] | int256 | int256 - The answer for the given round ID. | 71 | 72 | ### getTimestamp 73 | 74 | ```solidity 75 | function getTimestamp(uint256 roundId) external view returns (uint256) 76 | ``` 77 | 78 | Gets the timestamp for a specific round ID. 79 | 80 | #### Parameters 81 | 82 | | Name | Type | Description | 83 | | ------- | ------- | ---------------------------------------- | 84 | | roundId | uint256 | - The round ID to get the timestamp for. | 85 | 86 | #### Return Values 87 | 88 | | Name | Type | Description | 89 | | ---- | ------- | ----------------------------------------------- | 90 | | [0] | uint256 | uint256 - The timestamp for the given round ID. | 91 | 92 | ### AnswerUpdated 93 | 94 | ```solidity 95 | event AnswerUpdated(int256 current, uint256 roundId, uint256 updatedAt) 96 | ``` 97 | 98 | Emitted when the answer is updated. 99 | 100 | #### Parameters 101 | 102 | | Name | Type | Description | 103 | | --------- | ------- | ------------------------------------------------ | 104 | | current | int256 | - The updated answer. | 105 | | roundId | uint256 | - The round ID for which the answer was updated. | 106 | | updatedAt | uint256 | - The timestamp when the answer was updated. | 107 | 108 | ### NewRound 109 | 110 | ```solidity 111 | event NewRound(uint256 roundId, address startedBy, uint256 startedAt) 112 | ``` 113 | 114 | Emitted when a new round is started. 115 | 116 | #### Parameters 117 | 118 | | Name | Type | Description | 119 | | --------- | ------- | ---------------------------------------------------- | 120 | | roundId | uint256 | - The round ID of the new round. | 121 | | startedBy | address | - The address of the account that started the round. | 122 | | startedAt | uint256 | - The timestamp when the round was started. | 123 | -------------------------------------------------------------------------------- /api_reference/solidity/data-feeds/interfaces/AggregatorV2V3Interface.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## AggregatorV2V3Interface 4 | 5 | Interface that inherits from both AggregatorInterface and AggregatorV3Interface. 6 | -------------------------------------------------------------------------------- /api_reference/solidity/data-feeds/interfaces/AggregatorV3Interface.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## AggregatorV3Interface 4 | 5 | Interface for accessing detailed data from an aggregator contract, including 6 | round data and metadata. 7 | 8 | _Provides methods to get the latest data, historical data for specific rounds, 9 | and metadata such as decimals and description._ 10 | 11 | ### decimals 12 | 13 | ```solidity 14 | function decimals() external view returns (uint8) 15 | ``` 16 | 17 | Gets the number of decimals used by the aggregator. 18 | 19 | #### Return Values 20 | 21 | | Name | Type | Description | 22 | | ---- | ----- | ------------------------------- | 23 | | [0] | uint8 | uint8 - The number of decimals. | 24 | 25 | ### description 26 | 27 | ```solidity 28 | function description() external view returns (string) 29 | ``` 30 | 31 | Gets the description of the aggregator. 32 | 33 | #### Return Values 34 | 35 | | Name | Type | Description | 36 | | ---- | ------ | -------------------------------------------------- | 37 | | [0] | string | string memory - The description of the aggregator. | 38 | 39 | ### version 40 | 41 | ```solidity 42 | function version() external view returns (uint256) 43 | ``` 44 | 45 | Gets the version of the aggregator. 46 | 47 | #### Return Values 48 | 49 | | Name | Type | Description | 50 | | ---- | ------- | ---------------------------------------- | 51 | | [0] | uint256 | uint256 - The version of the aggregator. | 52 | 53 | ### getRoundData 54 | 55 | ```solidity 56 | function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 57 | ``` 58 | 59 | Gets the round data for a specific round ID. 60 | 61 | _This function should raise "No data present" if no data is available for the 62 | given round ID._ 63 | 64 | #### Parameters 65 | 66 | | Name | Type | Description | 67 | | --------- | ------ | ----------------------------------- | 68 | | \_roundId | uint80 | - The round ID to get the data for. | 69 | 70 | #### Return Values 71 | 72 | | Name | Type | Description | 73 | | --------------- | ------- | ------------------------------------------------ | 74 | | roundId | uint80 | - The round ID. | 75 | | answer | int256 | - The answer for the round. | 76 | | startedAt | uint256 | - The timestamp when the round started. | 77 | | updatedAt | uint256 | - The timestamp when the round was updated. | 78 | | answeredInRound | uint80 | - The round ID in which the answer was computed. | 79 | 80 | ### latestRoundData 81 | 82 | ```solidity 83 | function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 84 | ``` 85 | 86 | Gets the latest round data. 87 | 88 | _This function should raise "No data present" if no data is available._ 89 | 90 | #### Return Values 91 | 92 | | Name | Type | Description | 93 | | --------------- | ------- | ------------------------------------------------------- | 94 | | roundId | uint80 | - The latest round ID. | 95 | | answer | int256 | - The latest answer. | 96 | | startedAt | uint256 | - The timestamp when the latest round started. | 97 | | updatedAt | uint256 | - The timestamp when the latest round was updated. | 98 | | answeredInRound | uint80 | - The round ID in which the latest answer was computed. | 99 | -------------------------------------------------------------------------------- /api_reference/solidity/data-feeds/interfaces/index.mdx: -------------------------------------------------------------------------------- 1 | # Interfaces API Reference 2 | 3 | - [AggregatorInterface](AggregatorInterface.mdx) 4 | - [AggregatorV2V3Interface](AggregatorV2V3Interface.mdx) 5 | - [AggregatorV3Interface](AggregatorV3Interface.mdx) 6 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/DataStreamsLocalSimulator.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## DataStreamsLocalSimulator 4 | 5 | ### s_mockVerifier 6 | 7 | ```solidity 8 | contract MockVerifier s_mockVerifier 9 | ``` 10 | 11 | ### s_mockVerifierProxy 12 | 13 | ```solidity 14 | contract MockVerifierProxy s_mockVerifierProxy 15 | ``` 16 | 17 | ### s_mockFeeManager 18 | 19 | ```solidity 20 | contract MockFeeManager s_mockFeeManager 21 | ``` 22 | 23 | ### s_mockRewardManager 24 | 25 | ```solidity 26 | contract MockRewardManager s_mockRewardManager 27 | ``` 28 | 29 | ### i_wrappedNative 30 | 31 | ```solidity 32 | contract WETH9 i_wrappedNative 33 | ``` 34 | 35 | The wrapped native token instance 36 | 37 | ### i_linkToken 38 | 39 | ```solidity 40 | contract LinkToken i_linkToken 41 | ``` 42 | 43 | The LINK token instance 44 | 45 | ### constructor 46 | 47 | ```solidity 48 | constructor() public 49 | ``` 50 | 51 | ### requestLinkFromFaucet 52 | 53 | ```solidity 54 | function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) 55 | ``` 56 | 57 | Requests LINK tokens from the faucet. The provided amount of tokens are 58 | transferred to provided destination address. 59 | 60 | #### Parameters 61 | 62 | | Name | Type | Description | 63 | | ------ | ------- | -------------------------------------------------- | 64 | | to | address | - The address to which LINK tokens are to be sent. | 65 | | amount | uint256 | - The amount of LINK tokens to send. | 66 | 67 | #### Return Values 68 | 69 | | Name | Type | Description | 70 | | ------- | ---- | ----------------------------------------------------------------------------- | 71 | | success | bool | - Returns `true` if the transfer of tokens was successful, otherwise `false`. | 72 | 73 | ### configuration 74 | 75 | ```solidity 76 | function configuration() public view returns (contract WETH9 wrappedNative_, contract LinkToken linkToken_, contract MockVerifier mockVerifier_, contract MockVerifierProxy mockVerifierProxy_, contract MockFeeManager mockFeeManager_, contract MockRewardManager mockRewardManager_) 77 | ``` 78 | 79 | @notice Returns configuration details for pre-deployed contracts and services 80 | needed for local Data Streams simulations. 81 | 82 | #### Return Values 83 | 84 | | Name | Type | Description | 85 | | ------------------- | -------------------------- | ----------------------------------- | 86 | | wrappedNative\_ | contract WETH9 | - The wrapped native token. | 87 | | linkToken\_ | contract LinkToken | - The LINK token. | 88 | | mockVerifier\_ | contract MockVerifier | - The mock verifier contract. | 89 | | mockVerifierProxy\_ | contract MockVerifierProxy | - The mock verifier proxy contract. | 90 | | mockFeeManager\_ | contract MockFeeManager | - The mock fee manager contract. | 91 | | mockRewardManager\_ | contract MockRewardManager | - The mock reward manager contract. | 92 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/DataStreamsLocalSimulatorFork.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## DataStreamsLocalSimulatorFork 4 | 5 | ### i_register 6 | 7 | ```solidity 8 | contract Register i_register 9 | ``` 10 | 11 | The immutable register instance 12 | 13 | ### LINK_FAUCET 14 | 15 | ```solidity 16 | address LINK_FAUCET 17 | ``` 18 | 19 | The address of the LINK faucet 20 | 21 | ### constructor 22 | 23 | ```solidity 24 | constructor() public 25 | ``` 26 | 27 | Constructor to initialize the contract 28 | 29 | ### getNetworkDetails 30 | 31 | ```solidity 32 | function getNetworkDetails(uint256 chainId) external view returns (struct Register.NetworkDetails) 33 | ``` 34 | 35 | Returns the default values for currently Data Streams supported networks. If 36 | network is not present or some of the values are changed, user can manually add 37 | new network details using the `setNetworkDetails` function. 38 | 39 | #### Parameters 40 | 41 | | Name | Type | Description | 42 | | ------- | ------- | ----------------------------------------------------------------------------- | 43 | | chainId | uint256 | - The blockchain network chain ID. For example 11155111 for Ethereum Sepolia. | 44 | 45 | #### Return Values 46 | 47 | | Name | Type | Description | 48 | | ---- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | 49 | | [0] | struct Register.NetworkDetails | networkDetails - The tuple containing: verifierProxyAddress - The address of the Verifier Proxy smart contract. linkAddress - The address of the LINK token. | 50 | 51 | ### setNetworkDetails 52 | 53 | ```solidity 54 | function setNetworkDetails(uint256 chainId, struct Register.NetworkDetails networkDetails) external 55 | ``` 56 | 57 | If network details are not present or some of the values are changed, user can 58 | manually add new network details using the `setNetworkDetails` function. 59 | 60 | #### Parameters 61 | 62 | | Name | Type | Description | 63 | | -------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | 64 | | chainId | uint256 | - The blockchain network chain ID. For example 11155111 for Ethereum Sepolia. | 65 | | networkDetails | struct Register.NetworkDetails | - The tuple containing: verifierProxyAddress - The address of the Verifier Proxy smart contract. linkAddress - The address of the LINK token. | 66 | 67 | ### requestLinkFromFaucet 68 | 69 | ```solidity 70 | function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) 71 | ``` 72 | 73 | Requests LINK tokens from the faucet. The provided amount of tokens are 74 | transferred to provided destination address. 75 | 76 | #### Parameters 77 | 78 | | Name | Type | Description | 79 | | ------ | ------- | -------------------------------------------------- | 80 | | to | address | - The address to which LINK tokens are to be sent. | 81 | | amount | uint256 | - The amount of LINK tokens to send. | 82 | 83 | #### Return Values 84 | 85 | | Name | Type | Description | 86 | | ------- | ---- | ----------------------------------------------------------------------------- | 87 | | success | bool | - Returns `true` if the transfer of tokens was successful, otherwise `false`. | 88 | 89 | ### requestNativeFromFaucet 90 | 91 | ```solidity 92 | function requestNativeFromFaucet(address to, uint256 amount) external 93 | ``` 94 | 95 | Requests native coints from the faucet. 96 | 97 | #### Parameters 98 | 99 | | Name | Type | Description | 100 | | ------ | ------- | --------------------------------------------------- | 101 | | to | address | - The address to which native coins are to be sent. | 102 | | amount | uint256 | - The amount of native coins to send. | 103 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/MockFeeManager.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## Common 4 | 5 | ### Asset 6 | 7 | ```solidity 8 | struct Asset { 9 | address assetAddress; 10 | uint256 amount; 11 | } 12 | ``` 13 | 14 | ## MockFeeManager 15 | 16 | ### Unauthorized 17 | 18 | ```solidity 19 | error Unauthorized() 20 | ``` 21 | 22 | ### InvalidAddress 23 | 24 | ```solidity 25 | error InvalidAddress() 26 | ``` 27 | 28 | ### InvalidQuote 29 | 30 | ```solidity 31 | error InvalidQuote() 32 | ``` 33 | 34 | ### ExpiredReport 35 | 36 | ```solidity 37 | error ExpiredReport() 38 | ``` 39 | 40 | ### InvalidDiscount 41 | 42 | ```solidity 43 | error InvalidDiscount() 44 | ``` 45 | 46 | ### InvalidSurcharge 47 | 48 | ```solidity 49 | error InvalidSurcharge() 50 | ``` 51 | 52 | ### InvalidDeposit 53 | 54 | ```solidity 55 | error InvalidDeposit() 56 | ``` 57 | 58 | ### i_linkAddress 59 | 60 | ```solidity 61 | address i_linkAddress 62 | ``` 63 | 64 | ### i_nativeAddress 65 | 66 | ```solidity 67 | address i_nativeAddress 68 | ``` 69 | 70 | ### i_proxyAddress 71 | 72 | ```solidity 73 | address i_proxyAddress 74 | ``` 75 | 76 | ### i_rewardManager 77 | 78 | ```solidity 79 | contract IRewardManager i_rewardManager 80 | ``` 81 | 82 | ### s_nativeSurcharge 83 | 84 | ```solidity 85 | uint256 s_nativeSurcharge 86 | ``` 87 | 88 | ### s_mockDiscounts 89 | 90 | ```solidity 91 | mapping(address => uint256) s_mockDiscounts 92 | ``` 93 | 94 | ### onlyProxy 95 | 96 | ```solidity 97 | modifier onlyProxy() 98 | ``` 99 | 100 | ### constructor 101 | 102 | ```solidity 103 | constructor(address linkAddress, address nativeAddress, address proxyAddress, address rewardManager) public 104 | ``` 105 | 106 | ### processFee 107 | 108 | ```solidity 109 | function processFee(bytes payload, bytes parameterPayload, address subscriber) external payable 110 | ``` 111 | 112 | ### processFeeBulk 113 | 114 | ```solidity 115 | function processFeeBulk(bytes[] payloads, bytes parameterPayload, address subscriber) external payable 116 | ``` 117 | 118 | ### getFeeAndReward 119 | 120 | ```solidity 121 | function getFeeAndReward(address subscriber, bytes report, address quoteAddress) public view returns (struct Common.Asset, struct Common.Asset, uint256) 122 | ``` 123 | 124 | ### \_processFee 125 | 126 | ```solidity 127 | function _processFee(bytes payload, bytes parameterPayload, address subscriber) internal 128 | ``` 129 | 130 | ### setNativeSurcharge 131 | 132 | ```solidity 133 | function setNativeSurcharge(uint64 surcharge) external 134 | ``` 135 | 136 | ### setMockDiscount 137 | 138 | ```solidity 139 | function setMockDiscount(address subscriber, uint256 discount) external 140 | ``` 141 | 142 | ### getMockDiscount 143 | 144 | ```solidity 145 | function getMockDiscount(address subscriber) external view returns (uint256) 146 | ``` 147 | 148 | ### supportsInterface 149 | 150 | ```solidity 151 | function supportsInterface(bytes4 interfaceId) external pure returns (bool) 152 | ``` 153 | 154 | \_Returns true if this contract implements the interface defined by 155 | `interfaceId`. See the corresponding 156 | https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP 157 | section] to learn more about how these ids are created. 158 | 159 | This function call must use less than 30 000 gas.\_ 160 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/MockReportGenerator.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## MockReportGenerator 4 | 5 | ### i_donAddress 6 | 7 | ```solidity 8 | address i_donAddress 9 | ``` 10 | 11 | ### i_donDigest 12 | 13 | ```solidity 14 | uint256 i_donDigest 15 | ``` 16 | 17 | ### i_reportV2MockFeedId 18 | 19 | ```solidity 20 | bytes32 i_reportV2MockFeedId 21 | ``` 22 | 23 | ### i_reportV3MockFeedId 24 | 25 | ```solidity 26 | bytes32 i_reportV3MockFeedId 27 | ``` 28 | 29 | ### i_reportV4MockFeedId 30 | 31 | ```solidity 32 | bytes32 i_reportV4MockFeedId 33 | ``` 34 | 35 | ### s_price 36 | 37 | ```solidity 38 | int192 s_price 39 | ``` 40 | 41 | ### s_bid 42 | 43 | ```solidity 44 | int192 s_bid 45 | ``` 46 | 47 | ### s_ask 48 | 49 | ```solidity 50 | int192 s_ask 51 | ``` 52 | 53 | ### s_expiresPeriod 54 | 55 | ```solidity 56 | uint32 s_expiresPeriod 57 | ``` 58 | 59 | ### s_marketStatus 60 | 61 | ```solidity 62 | uint32 s_marketStatus 63 | ``` 64 | 65 | ### s_nativeFee 66 | 67 | ```solidity 68 | uint192 s_nativeFee 69 | ``` 70 | 71 | ### s_linkFee 72 | 73 | ```solidity 74 | uint192 s_linkFee 75 | ``` 76 | 77 | ### MockReportGenerator\_\_InvalidBid 78 | 79 | ```solidity 80 | error MockReportGenerator__InvalidBid() 81 | ``` 82 | 83 | ### MockReportGenerator\_\_InvalidAsk 84 | 85 | ```solidity 86 | error MockReportGenerator__InvalidAsk() 87 | ``` 88 | 89 | ### MockReportGenerator\_\_CastOverflow 90 | 91 | ```solidity 92 | error MockReportGenerator__CastOverflow() 93 | ``` 94 | 95 | ### constructor 96 | 97 | ```solidity 98 | constructor(int192 initialPrice) public 99 | ``` 100 | 101 | ### generateReport 102 | 103 | ```solidity 104 | function generateReport(struct ReportVersions.ReportV2 report) external returns (bytes signedReport) 105 | ``` 106 | 107 | ### generateReport 108 | 109 | ```solidity 110 | function generateReport(struct ReportVersions.ReportV3 report) external returns (bytes signedReport) 111 | ``` 112 | 113 | ### generateReport 114 | 115 | ```solidity 116 | function generateReport(struct ReportVersions.ReportV4 report) external returns (bytes signedReport) 117 | ``` 118 | 119 | ### generateReportV2 120 | 121 | ```solidity 122 | function generateReportV2() external returns (bytes signedReport, struct ReportVersions.ReportV2 report) 123 | ``` 124 | 125 | ### generateReportV3 126 | 127 | ```solidity 128 | function generateReportV3() external returns (bytes signedReport, struct ReportVersions.ReportV3 report) 129 | ``` 130 | 131 | ### generateReportV4 132 | 133 | ```solidity 134 | function generateReportV4() external returns (bytes signedReport, struct ReportVersions.ReportV4 report) 135 | ``` 136 | 137 | ### updatePrice 138 | 139 | ```solidity 140 | function updatePrice(int192 price) public 141 | ``` 142 | 143 | ### updatePriceBidAndAsk 144 | 145 | ```solidity 146 | function updatePriceBidAndAsk(int192 price, int192 bid, int192 ask) external 147 | ``` 148 | 149 | ### updateExpiresPeriod 150 | 151 | ```solidity 152 | function updateExpiresPeriod(uint32 period) external 153 | ``` 154 | 155 | ### updateMarketStatus 156 | 157 | ```solidity 158 | function updateMarketStatus(uint32 status) external 159 | ``` 160 | 161 | ### updateFees 162 | 163 | ```solidity 164 | function updateFees(uint192 nativeFee, uint192 linkFee) external 165 | ``` 166 | 167 | ### getMockDonAddress 168 | 169 | ```solidity 170 | function getMockDonAddress() external view returns (address) 171 | ``` 172 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/MockRewardManager.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## MockRewardManager 4 | 5 | ### Unauthorized 6 | 7 | ```solidity 8 | error Unauthorized() 9 | ``` 10 | 11 | ### i_linkAddress 12 | 13 | ```solidity 14 | address i_linkAddress 15 | ``` 16 | 17 | ### s_feeManagerAddress 18 | 19 | ```solidity 20 | address s_feeManagerAddress 21 | ``` 22 | 23 | ### FeePaid 24 | 25 | ```solidity 26 | event FeePaid(struct IRewardManager.FeePayment[] payments, address payer) 27 | ``` 28 | 29 | ### onlyFeeManager 30 | 31 | ```solidity 32 | modifier onlyFeeManager() 33 | ``` 34 | 35 | ### constructor 36 | 37 | ```solidity 38 | constructor(address linkAddress) public 39 | ``` 40 | 41 | ### onFeePaid 42 | 43 | ```solidity 44 | function onFeePaid(struct IRewardManager.FeePayment[] payments, address payer) external 45 | ``` 46 | 47 | ### claimRewards 48 | 49 | ```solidity 50 | function claimRewards(bytes32[]) external pure 51 | ``` 52 | 53 | ### setFeeManager 54 | 55 | ```solidity 56 | function setFeeManager(address newFeeManager) external 57 | ``` 58 | 59 | ### supportsInterface 60 | 61 | ```solidity 62 | function supportsInterface(bytes4 interfaceId) external pure returns (bool) 63 | ``` 64 | 65 | \_Returns true if this contract implements the interface defined by 66 | `interfaceId`. See the corresponding 67 | https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP 68 | section] to learn more about how these ids are created. 69 | 70 | This function call must use less than 30 000 gas.\_ 71 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/MockVerifier.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## MockVerifier 4 | 5 | ### AccessForbidden 6 | 7 | ```solidity 8 | error AccessForbidden() 9 | ``` 10 | 11 | ### InactiveFeed 12 | 13 | ```solidity 14 | error InactiveFeed(bytes32 feedId) 15 | ``` 16 | 17 | ### DigestInactive 18 | 19 | ```solidity 20 | error DigestInactive(bytes32 feedId, bytes32 configDigest) 21 | ``` 22 | 23 | ### BadVerification 24 | 25 | ```solidity 26 | error BadVerification() 27 | ``` 28 | 29 | ### InvalidV 30 | 31 | ```solidity 32 | error InvalidV() 33 | ``` 34 | 35 | ### AlreadyUsedSignature 36 | 37 | ```solidity 38 | error AlreadyUsedSignature() 39 | ``` 40 | 41 | ### InvalidSignatureLength 42 | 43 | ```solidity 44 | error InvalidSignatureLength() 45 | ``` 46 | 47 | ### InvalidS 48 | 49 | ```solidity 50 | error InvalidS() 51 | ``` 52 | 53 | ### N_2 54 | 55 | ```solidity 56 | uint256 N_2 57 | ``` 58 | 59 | ### MOCK_DATA_STREAM_DON_ADDRESS 60 | 61 | ```solidity 62 | address MOCK_DATA_STREAM_DON_ADDRESS 63 | ``` 64 | 65 | ### i_verifierProxy 66 | 67 | ```solidity 68 | address i_verifierProxy 69 | ``` 70 | 71 | ### s_inactiveDigests 72 | 73 | ```solidity 74 | mapping(bytes32 => mapping(bytes32 => bool)) s_inactiveDigests 75 | ``` 76 | 77 | ### s_inactiveFeeds 78 | 79 | ```solidity 80 | mapping(bytes32 => bool) s_inactiveFeeds 81 | ``` 82 | 83 | ### s_verifiedSignatures 84 | 85 | ```solidity 86 | mapping(bytes => bool) s_verifiedSignatures 87 | ``` 88 | 89 | ### ReportVerified 90 | 91 | ```solidity 92 | event ReportVerified(bytes32 feedId, address sender) 93 | ``` 94 | 95 | ### constructor 96 | 97 | ```solidity 98 | constructor(address verifierProxy) public 99 | ``` 100 | 101 | ### verify 102 | 103 | ```solidity 104 | function verify(bytes signedReport, address sender) external returns (bytes verifierResponse) 105 | ``` 106 | 107 | ### deactivateConfig 108 | 109 | ```solidity 110 | function deactivateConfig(bytes32 feedId, bytes32 configDigest) external 111 | ``` 112 | 113 | ### activateConfig 114 | 115 | ```solidity 116 | function activateConfig(bytes32 feedId, bytes32 configDigest) external 117 | ``` 118 | 119 | ### deactivateFeed 120 | 121 | ```solidity 122 | function deactivateFeed(bytes32 feedId) external 123 | ``` 124 | 125 | ### activateFeed 126 | 127 | ```solidity 128 | function activateFeed(bytes32 feedId) external 129 | ``` 130 | 131 | ### supportsInterface 132 | 133 | ```solidity 134 | function supportsInterface(bytes4 interfaceId) external pure returns (bool) 135 | ``` 136 | 137 | \_Returns true if this contract implements the interface defined by 138 | `interfaceId`. See the corresponding 139 | https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP 140 | section] to learn more about how these ids are created. 141 | 142 | This function call must use less than 30 000 gas.\_ 143 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/MockVerifierProxy.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## MockVerifierProxy 4 | 5 | ### ZeroAddress 6 | 7 | ```solidity 8 | error ZeroAddress() 9 | ``` 10 | 11 | ### VerifierInvalid 12 | 13 | ```solidity 14 | error VerifierInvalid() 15 | ``` 16 | 17 | ### VerifierNotFound 18 | 19 | ```solidity 20 | error VerifierNotFound() 21 | ``` 22 | 23 | ### s_verifier 24 | 25 | ```solidity 26 | address s_verifier 27 | ``` 28 | 29 | ### s_feeManager 30 | 31 | ```solidity 32 | contract IVerifierFeeManager s_feeManager 33 | ``` 34 | 35 | ### VerifierInitialized 36 | 37 | ```solidity 38 | event VerifierInitialized(address verifierAddress) 39 | ``` 40 | 41 | ### onlyValidVerifier 42 | 43 | ```solidity 44 | modifier onlyValidVerifier(address verifierAddress) 45 | ``` 46 | 47 | ### verify 48 | 49 | ```solidity 50 | function verify(bytes payload, bytes parameterPayload) external payable returns (bytes) 51 | ``` 52 | 53 | ### verifyBulk 54 | 55 | ```solidity 56 | function verifyBulk(bytes[] payloads, bytes parameterPayload) external payable returns (bytes[] verifiedReports) 57 | ``` 58 | 59 | ### \_verify 60 | 61 | ```solidity 62 | function _verify(bytes payload) internal returns (bytes) 63 | ``` 64 | 65 | ### initializeVerifier 66 | 67 | ```solidity 68 | function initializeVerifier(address verifierAddress) external 69 | ``` 70 | 71 | ### getVerifier 72 | 73 | ```solidity 74 | function getVerifier(bytes32) external view returns (address) 75 | ``` 76 | 77 | ### setFeeManager 78 | 79 | ```solidity 80 | function setFeeManager(contract IVerifierFeeManager feeManager) external 81 | ``` 82 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/Register.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## Register 4 | 5 | This contract allows storing and retrieving network details for various chains. 6 | 7 | _Stores network details in a mapping based on chain IDs._ 8 | 9 | ### NetworkDetails 10 | 11 | ```solidity 12 | struct NetworkDetails { 13 | address verifierProxyAddress; 14 | address linkAddress; 15 | } 16 | ``` 17 | 18 | ### s_networkDetails 19 | 20 | ```solidity 21 | mapping(uint256 => struct Register.NetworkDetails) s_networkDetails 22 | ``` 23 | 24 | Mapping to store network details based on chain ID. 25 | 26 | ### constructor 27 | 28 | ```solidity 29 | constructor() public 30 | ``` 31 | 32 | Constructor to initialize the network details for various chains. 33 | 34 | ### getNetworkDetails 35 | 36 | ```solidity 37 | function getNetworkDetails(uint256 chainId) external view returns (struct Register.NetworkDetails networkDetails) 38 | ``` 39 | 40 | Retrieves network details for a given chain ID. 41 | 42 | #### Parameters 43 | 44 | | Name | Type | Description | 45 | | ------- | ------- | --------------------------------------------- | 46 | | chainId | uint256 | - The ID of the chain to get the details for. | 47 | 48 | #### Return Values 49 | 50 | | Name | Type | Description | 51 | | -------------- | ------------------------------ | ------------------------------------------------- | 52 | | networkDetails | struct Register.NetworkDetails | - The network details for the specified chain ID. | 53 | 54 | ### setNetworkDetails 55 | 56 | ```solidity 57 | function setNetworkDetails(uint256 chainId, struct Register.NetworkDetails networkDetails) external 58 | ``` 59 | 60 | Sets the network details for a given chain ID. 61 | 62 | #### Parameters 63 | 64 | | Name | Type | Description | 65 | | -------------- | ------------------------------ | -------------------------------------------------------- | 66 | | chainId | uint256 | - The ID of the chain to set the details for. | 67 | | networkDetails | struct Register.NetworkDetails | - The network details to set for the specified chain ID. | 68 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/ReportVersions.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## ReportVersions 4 | 5 | ### ReportV2 6 | 7 | _Represents a data report from a Data Streams stream for v2 schema (crypto 8 | streams). The `price` value is carried to either 8 or 18 decimal places, 9 | depending on the stream. For more information, see 10 | https://docs.chain.link/data-streams/crypto-streams and 11 | https://docs.chain.link/data-streams/reference/report-schema_ 12 | 13 | ```solidity 14 | struct ReportV2 { 15 | bytes32 feedId; 16 | uint32 validFromTimestamp; 17 | uint32 observationsTimestamp; 18 | uint192 nativeFee; 19 | uint192 linkFee; 20 | uint32 expiresAt; 21 | int192 benchmarkPrice; 22 | } 23 | ``` 24 | 25 | ### ReportV3 26 | 27 | _Represents a data report from a Data Streams stream for v3 schema (crypto 28 | streams). The `price`, `bid`, and `ask` values are carried to either 8 or 18 29 | decimal places, depending on the stream. For more information, see 30 | https://docs.chain.link/data-streams/crypto-streams and 31 | https://docs.chain.link/data-streams/reference/report-schema_ 32 | 33 | ```solidity 34 | struct ReportV3 { 35 | bytes32 feedId; 36 | uint32 validFromTimestamp; 37 | uint32 observationsTimestamp; 38 | uint192 nativeFee; 39 | uint192 linkFee; 40 | uint32 expiresAt; 41 | int192 price; 42 | int192 bid; 43 | int192 ask; 44 | } 45 | ``` 46 | 47 | ### ReportV4 48 | 49 | _Represents a data report from a Data Streams stream for v4 schema (RWA stream). 50 | The `price` value is carried to either 8 or 18 decimal places, depending on the 51 | stream. The `marketStatus` indicates whether the market is currently open. 52 | Possible values: `0` (`Unknown`), `1` (`Closed`), `2` (`Open`). For more 53 | information, see https://docs.chain.link/data-streams/rwa-streams and 54 | https://docs.chain.link/data-streams/reference/report-schema-v4_ 55 | 56 | ```solidity 57 | struct ReportV4 { 58 | bytes32 feedId; 59 | uint32 validFromTimestamp; 60 | uint32 observationsTimestamp; 61 | uint192 nativeFee; 62 | uint192 linkFee; 63 | uint32 expiresAt; 64 | int192 price; 65 | uint32 marketStatus; 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/index.mdx: -------------------------------------------------------------------------------- 1 | # Data-streams API Reference 2 | 3 | - [DataStreamsLocalSimulator](DataStreamsLocalSimulator.mdx) 4 | - [DataStreamsLocalSimulatorFork](DataStreamsLocalSimulatorFork.mdx) 5 | - [MockFeeManager](MockFeeManager.mdx) 6 | - [MockReportGenerator](MockReportGenerator.mdx) 7 | - [MockRewardManager](MockRewardManager.mdx) 8 | - [MockVerifier](MockVerifier.mdx) 9 | - [MockVerifierProxy](MockVerifierProxy.mdx) 10 | - [Register](Register.mdx) 11 | - [ReportVersions](ReportVersions.mdx) 12 | - [interfaces](interfaces/index.mdx) 13 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/interfaces/IRewardManager.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## IRewardManager 4 | 5 | ### onFeePaid 6 | 7 | ```solidity 8 | function onFeePaid(struct IRewardManager.FeePayment[] payments, address payee) external 9 | ``` 10 | 11 | ### claimRewards 12 | 13 | ```solidity 14 | function claimRewards(bytes32[] poolIds) external 15 | ``` 16 | 17 | ### setFeeManager 18 | 19 | ```solidity 20 | function setFeeManager(address newFeeManager) external 21 | ``` 22 | 23 | ### FeePayment 24 | 25 | ```solidity 26 | struct FeePayment { 27 | bytes32 poolId; 28 | uint192 amount; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/interfaces/IVerifier.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## IVerifier 4 | 5 | ### verify 6 | 7 | ```solidity 8 | function verify(bytes signedReport, address sender) external returns (bytes verifierResponse) 9 | ``` 10 | 11 | ### activateConfig 12 | 13 | ```solidity 14 | function activateConfig(bytes32 feedId, bytes32 configDigest) external 15 | ``` 16 | 17 | ### deactivateConfig 18 | 19 | ```solidity 20 | function deactivateConfig(bytes32 feedId, bytes32 configDigest) external 21 | ``` 22 | 23 | ### activateFeed 24 | 25 | ```solidity 26 | function activateFeed(bytes32 feedId) external 27 | ``` 28 | 29 | ### deactivateFeed 30 | 31 | ```solidity 32 | function deactivateFeed(bytes32 feedId) external 33 | ``` 34 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/interfaces/IVerifierFeeManager.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## IVerifierFeeManager 4 | 5 | ### processFee 6 | 7 | ```solidity 8 | function processFee(bytes payload, bytes parameterPayload, address subscriber) external payable 9 | ``` 10 | 11 | ### processFeeBulk 12 | 13 | ```solidity 14 | function processFeeBulk(bytes[] payloads, bytes parameterPayload, address subscriber) external payable 15 | ``` 16 | -------------------------------------------------------------------------------- /api_reference/solidity/data-streams/interfaces/index.mdx: -------------------------------------------------------------------------------- 1 | # Interfaces API Reference 2 | 3 | - [IRewardManager](IRewardManager.mdx) 4 | - [IVerifier](IVerifier.mdx) 5 | - [IVerifierFeeManager](IVerifierFeeManager.mdx) 6 | -------------------------------------------------------------------------------- /api_reference/solidity/index.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API Reference 2 | 3 | - [ccip](ccip/index.mdx) 4 | - [data-feeds](data-feeds/index.mdx) 5 | - [data-streams](data-streams/index.mdx) 6 | - [shared](shared/index.mdx) 7 | -------------------------------------------------------------------------------- /api_reference/solidity/shared/LinkToken.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## LinkToken 4 | 5 | This contract implements the ChainLink Token (LINK) using the ERC677 standard. 6 | 7 | _Inherits from the ERC677 token contract and initializes with a fixed total 8 | supply and standard token details._ 9 | 10 | ### constructor 11 | 12 | ```solidity 13 | constructor() public 14 | ``` 15 | 16 | Constructor to initialize the LinkToken contract with a fixed total supply, 17 | name, and symbol. 18 | 19 | _Calls the ERC677 constructor with the name and symbol, and then mints the total 20 | supply to the contract deployer._ 21 | 22 | ### \_onCreate 23 | 24 | ```solidity 25 | function _onCreate() internal virtual 26 | ``` 27 | 28 | Hook that is called when this contract is created. 29 | 30 | _Useful to override constructor behaviour in child contracts (e.g., LINK bridge 31 | tokens). The default implementation mints 10\*\*27 tokens to the contract 32 | deployer._ 33 | -------------------------------------------------------------------------------- /api_reference/solidity/shared/WETH9.mdx: -------------------------------------------------------------------------------- 1 | # Solidity API 2 | 3 | ## WETH9 4 | 5 | ### name 6 | 7 | ```solidity 8 | string name 9 | ``` 10 | 11 | ### symbol 12 | 13 | ```solidity 14 | string symbol 15 | ``` 16 | 17 | ### decimals 18 | 19 | ```solidity 20 | uint8 decimals 21 | ``` 22 | 23 | ### Approval 24 | 25 | ```solidity 26 | event Approval(address src, address guy, uint256 wad) 27 | ``` 28 | 29 | ### Transfer 30 | 31 | ```solidity 32 | event Transfer(address src, address dst, uint256 wad) 33 | ``` 34 | 35 | ### Deposit 36 | 37 | ```solidity 38 | event Deposit(address dst, uint256 wad) 39 | ``` 40 | 41 | ### Withdrawal 42 | 43 | ```solidity 44 | event Withdrawal(address src, uint256 wad) 45 | ``` 46 | 47 | ### balanceOf 48 | 49 | ```solidity 50 | mapping(address => uint256) balanceOf 51 | ``` 52 | 53 | ### allowance 54 | 55 | ```solidity 56 | mapping(address => mapping(address => uint256)) allowance 57 | ``` 58 | 59 | ### receive 60 | 61 | ```solidity 62 | receive() external payable 63 | ``` 64 | 65 | ### \_deposit 66 | 67 | ```solidity 68 | function _deposit() internal 69 | ``` 70 | 71 | ### deposit 72 | 73 | ```solidity 74 | function deposit() external payable 75 | ``` 76 | 77 | ### withdraw 78 | 79 | ```solidity 80 | function withdraw(uint256 wad) external 81 | ``` 82 | 83 | ### totalSupply 84 | 85 | ```solidity 86 | function totalSupply() public view returns (uint256) 87 | ``` 88 | 89 | ### approve 90 | 91 | ```solidity 92 | function approve(address guy, uint256 wad) public returns (bool) 93 | ``` 94 | 95 | ### transfer 96 | 97 | ```solidity 98 | function transfer(address dst, uint256 wad) public returns (bool) 99 | ``` 100 | 101 | ### transferFrom 102 | 103 | ```solidity 104 | function transferFrom(address src, address dst, uint256 wad) public returns (bool) 105 | ``` 106 | -------------------------------------------------------------------------------- /api_reference/solidity/shared/index.mdx: -------------------------------------------------------------------------------- 1 | # Shared API Reference 2 | 3 | - [LinkToken](LinkToken.mdx) 4 | - [WETH9](WETH9.mdx) 5 | -------------------------------------------------------------------------------- /assets/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcontractkit/chainlink-local/e06bc4085c6f3607f6a83fa19c2d24033e949993/assets/thumbnail.png -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | solc = '0.8.24' 4 | evm_version = 'paris' 5 | out = 'out' 6 | libs = ['lib'] 7 | test = 'test' 8 | cache_path = 'cache_forge' 9 | remappings = [ 10 | '@chainlink/contracts-ccip/=lib/ccip/contracts/', 11 | '@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/', 12 | '@chainlink/local/src/=src/', 13 | ] 14 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import "solidity-docgen"; 3 | 4 | import { HardhatUserConfig } from "hardhat/config"; 5 | import "@nomicfoundation/hardhat-toolbox"; 6 | import "@nomicfoundation/hardhat-foundry"; 7 | 8 | dotenv.config(); 9 | 10 | const config: HardhatUserConfig = { 11 | solidity: { 12 | compilers: [ 13 | { 14 | version: "0.8.24", 15 | settings: { 16 | evmVersion: "paris" 17 | }, 18 | } 19 | ] 20 | }, 21 | paths: { 22 | sources: "./src", 23 | }, 24 | docgen: { 25 | pages: "files", 26 | pageExtension: ".mdx", 27 | exclude: ["test"], 28 | outputDir: "api_reference/solidity", 29 | }, 30 | }; 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /helper_doc/generate-index-files.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs-extra"; 2 | import path from "path"; 3 | 4 | const MDX_EXTENSION = ".mdx"; 5 | const rootDir = path.join(process.cwd(), "api_reference"); 6 | 7 | const generateIndexFile = async (dir: string, header: string) => { 8 | const files = await fs.readdir(dir); 9 | const mdxFiles = files.filter( 10 | (file) => file.endsWith(MDX_EXTENSION) && file !== `index${MDX_EXTENSION}` 11 | ); 12 | const subDirs = files.filter((file) => 13 | fs.lstatSync(path.join(dir, file)).isDirectory() 14 | ); 15 | 16 | let content = `# ${header}\n\n`; 17 | 18 | const allEntries = [...mdxFiles, ...subDirs]; 19 | 20 | if (allEntries.length > 0) { 21 | allEntries.forEach((entry) => { 22 | const fileNameWithoutExtension = path.basename(entry, MDX_EXTENSION); 23 | const linkName = entry.endsWith(MDX_EXTENSION) 24 | ? fileNameWithoutExtension 25 | : entry; 26 | const linkPath = entry.endsWith(MDX_EXTENSION) 27 | ? entry 28 | : `${entry}/index${MDX_EXTENSION}`; 29 | content += `- [${linkName}](${linkPath})\n`; 30 | }); 31 | } 32 | 33 | await fs.writeFile(path.join(dir, `index${MDX_EXTENSION}`), content); 34 | }; 35 | 36 | const traverseDirectory = async (dir: string, header: string) => { 37 | await generateIndexFile(dir, header); 38 | 39 | const files = await fs.readdir(dir); 40 | const subDirs = files.filter((file) => 41 | fs.lstatSync(path.join(dir, file)).isDirectory() 42 | ); 43 | 44 | for (const subDir of subDirs) { 45 | await traverseDirectory( 46 | path.join(dir, subDir), 47 | `${subDir.charAt(0).toUpperCase() + subDir.slice(1)} API Reference` 48 | ); 49 | } 50 | }; 51 | 52 | traverseDirectory(rootDir, "API Reference") 53 | .then(() => console.log("Index files generated successfully.")) 54 | .catch((err) => console.error(err)); 55 | -------------------------------------------------------------------------------- /helper_doc/generate-jsdoc.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import jsdoc2md from 'jsdoc-to-markdown'; 4 | 5 | const MDX_EXTENSION = '.mdx'; 6 | const outputDir = path.join(process.cwd(), 'api_reference/javascript'); 7 | 8 | const jsFiles = ['scripts/CCIPLocalSimulatorFork.js']; 9 | 10 | const generateMarkdownDocs = async ( 11 | files: string[], 12 | outputDirectory: string 13 | ) => { 14 | await fs.ensureDir(outputDirectory); 15 | 16 | for (const file of files) { 17 | const absoluteFilePath = path.join(process.cwd(), file); 18 | const fileName = path.basename(file, path.extname(file)); 19 | const outputPath = path.join( 20 | outputDirectory, 21 | `${fileName}${MDX_EXTENSION}` 22 | ); 23 | const markdown = await jsdoc2md.render({ files: absoluteFilePath }); 24 | const fixedMarkdown = markdown.replace(/<\{/g, '<\\{'); 25 | await fs.outputFile(outputPath, fixedMarkdown); 26 | } 27 | }; 28 | 29 | generateMarkdownDocs(jsFiles, outputDir) 30 | .then(() => console.log('Markdown documentation generated successfully.')) 31 | .catch(err => console.error(err)); 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainlink/local", 3 | "description": "Chainlink Local Simulator", 4 | "license": "MIT", 5 | "version": "0.2.4", 6 | "files": [ 7 | "src/**/*.sol", 8 | "!src/test/**/*", 9 | "scripts/ccipLocalSimulatorFork.js", 10 | "scripts/data-streams/**/*.js", 11 | "abi/**/*.json" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/smartcontractkit/chainlink-local.git" 16 | }, 17 | "keywords": [ 18 | "chainlink" 19 | ], 20 | "scripts": { 21 | "hardhat-compile": "npx hardhat compile", 22 | "forge-compile": "forge build", 23 | "hardhat-test": "npx hardhat test", 24 | "forge-test": "forge test", 25 | "test": "npm run hardhat-test && npm run forge-test", 26 | "prettier": "prettier --write 'api_reference/**/*'", 27 | "generate-solidity": "rimraf api_reference/solidity && npm run hardhat-compile && npm run forge-compile && npx hardhat docgen", 28 | "generate-jsdoc": "rimraf api_reference/javascript && npx ts-node helper_doc/generate-jsdoc.ts", 29 | "generate-index": "npx ts-node helper_doc/generate-index-files.ts", 30 | "generate-docs": "npm run generate-solidity && npm run generate-jsdoc && npm run generate-index && npm run prettier" 31 | }, 32 | "devDependencies": { 33 | "@nomicfoundation/hardhat-foundry": "^1.1.1", 34 | "@nomicfoundation/hardhat-toolbox": "^4.0.0", 35 | "@types/fs-extra": "^11.0.4", 36 | "@types/jsdoc-to-markdown": "^7.0.6", 37 | "dotenv": "^16.4.5", 38 | "fs-extra": "^11.2.0", 39 | "hardhat": "^2.20.1", 40 | "jsdoc-to-markdown": "^8.0.1", 41 | "prettier": "^3.3.3", 42 | "rimraf": "^6.0.1", 43 | "solidity-docgen": "^0.6.0-beta.36" 44 | }, 45 | "dependencies": { 46 | "@chainlink/contracts": "^1.3.0", 47 | "@chainlink/contracts-ccip": "^1.5.1-beta.0" 48 | } 49 | } -------------------------------------------------------------------------------- /scripts/CCIPLocalSimulatorFork.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | const { setBalance } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); 3 | 4 | const RouterAbi = require("../abi/Router.json"); 5 | const LinkTokenAbi = require("../abi/LinkToken.json"); 6 | const EVM2EVMOnRampAbi = require("../abi/EVM2EVMOnRamp.json"); 7 | const EVM2EVMOffRampAbi = require("../abi/EVM2EVMOffRamp.json"); 8 | 9 | /** 10 | * Requests LINK tokens from the faucet and returns the transaction hash 11 | * 12 | * @param {string} linkAddress The address of the LINK contract on the current network 13 | * @param {string} to The address to send LINK to 14 | * @param {bigint} amount The amount of LINK to request 15 | * @returns {Promise} Promise resolving to the transaction hash of the fund transfer 16 | */ 17 | async function requestLinkFromTheFaucet(linkAddress, to, amount) { 18 | const LINK_FAUCET_ADDRESS = `0x4281eCF07378Ee595C564a59048801330f3084eE`; 19 | const linkFaucetImpersonated = await ethers.getImpersonatedSigner(LINK_FAUCET_ADDRESS); 20 | 21 | const linkToken = new ethers.Contract(linkAddress, LinkTokenAbi, ethers.provider); 22 | const tx = await linkToken.connect(linkFaucetImpersonated).transfer(to, amount); 23 | 24 | return tx.hash; 25 | } 26 | 27 | /** 28 | * @typedef {Object} Evm2EvmMessage 29 | * @property {bigint} sourceChainSelector 30 | * @property {string} sender 31 | * @property {string} receiver 32 | * @property {bigint} sequenceNumber 33 | * @property {bigint} gasLimit 34 | * @property {boolean} strict 35 | * @property {bigint} nonce 36 | * @property {string} feeToken 37 | * @property {bigint} feeTokenAmount 38 | * @property {string} data 39 | * @property {Array<{token: string, amount: bigint}>} tokenAmounts 40 | * @property {Array} sourceTokenData 41 | * @property {string} messageId 42 | */ 43 | 44 | /** 45 | * Parses a transaction receipt to extract the sent message 46 | * Scans through transaction logs to find a `CCIPSendRequested` event and then decodes it to an object 47 | * 48 | * @param {object} receipt - The transaction receipt from the `ccipSend` call 49 | * @returns {Evm2EvmMessage | null} Returns either the sent message or null if provided receipt does not contain `CCIPSendRequested` log 50 | */ 51 | function getEvm2EvmMessage(receipt) { 52 | const evm2EvmOnRampInterface = new ethers.Interface(EVM2EVMOnRampAbi); 53 | 54 | for (const log of receipt.logs) { 55 | try { 56 | const parsedLog = evm2EvmOnRampInterface.parseLog(log); 57 | if (parsedLog?.name == `CCIPSendRequested`) { 58 | const [ 59 | sourceChainSelector, 60 | sender, 61 | receiver, 62 | sequenceNumber, 63 | gasLimit, 64 | strict, 65 | nonce, 66 | feeToken, 67 | feeTokenAmount, 68 | data, 69 | tokenAmountsRaw, 70 | sourceTokenDataRaw, 71 | messageId, 72 | ] = parsedLog?.args[0]; 73 | const tokenAmounts = tokenAmountsRaw.map(([token, amount]) => ({ 74 | token, 75 | amount, 76 | })); 77 | const sourceTokenData = sourceTokenDataRaw.map(data => data); 78 | const evm2EvmMessage = { 79 | sourceChainSelector, 80 | sender, 81 | receiver, 82 | sequenceNumber, 83 | gasLimit, 84 | strict, 85 | nonce, 86 | feeToken, 87 | feeTokenAmount, 88 | data, 89 | tokenAmounts, 90 | sourceTokenData, 91 | messageId, 92 | }; 93 | return evm2EvmMessage; 94 | } 95 | } catch (error) { 96 | return null; 97 | } 98 | } 99 | 100 | return null; 101 | } 102 | 103 | /** 104 | * Routes the sent message from the source network on the destination (current) network 105 | * 106 | * @param {string} routerAddress - Address of the destination Router 107 | * @param {Evm2EvmMessage} evm2EvmMessage - Sent cross-chain message 108 | * @returns {Promise} Either resolves with no value if the message is successfully routed, or reverts 109 | * @throws {Error} Fails if no off-ramp matches the message's source chain selector or if calling `router.getOffRamps()` 110 | */ 111 | async function routeMessage(routerAddress, evm2EvmMessage) { 112 | const router = new ethers.Contract(routerAddress, RouterAbi, ethers.provider); 113 | 114 | let offRamps; 115 | 116 | try { 117 | const offRampsRaw = await router.getOffRamps(); 118 | offRamps = offRampsRaw.map(([sourceChainSelector, offRamp]) => ({ sourceChainSelector, offRamp })); 119 | } catch (error) { 120 | throw new Error(`Calling router.getOffRamps threw the following error: ${error}`); 121 | } 122 | 123 | for (const offRamp of offRamps) { 124 | if (offRamp.sourceChainSelector == evm2EvmMessage.sourceChainSelector) { 125 | const evm2EvmOffRamp = new ethers.Contract(offRamp.offRamp, EVM2EVMOffRampAbi); 126 | 127 | const self = await ethers.getImpersonatedSigner(offRamp.offRamp); 128 | await setBalance(self.address, BigInt(100) ** BigInt(18)); 129 | 130 | const offchainTokenData = new Array(evm2EvmMessage.tokenAmounts.length).fill("0x"); 131 | 132 | await evm2EvmOffRamp.connect(self).executeSingleMessage(evm2EvmMessage, offchainTokenData); 133 | 134 | return; 135 | } 136 | } 137 | 138 | throw new Error(`No offRamp contract found, message has not been routed. Check your input parameters please`); 139 | } 140 | 141 | module.exports = { 142 | requestLinkFromTheFaucet, 143 | getEvm2EvmMessage, 144 | routeMessage 145 | }; 146 | -------------------------------------------------------------------------------- /scripts/data-streams/DataStreamsLocalSimulatorFork.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | const { setBalance } = require("@nomicfoundation/hardhat-toolbox/network-helpers"); 3 | 4 | const LinkTokenAbi = require("../../abi/LinkToken.json"); 5 | 6 | /** 7 | * Requests LINK tokens from the faucet and returns the transaction hash 8 | * 9 | * @param {string} linkAddress The address of the LINK contract on the current network 10 | * @param {string} to The address to send LINK to 11 | * @param {bigint} amount The amount of LINK to request 12 | * @returns {Promise} Promise resolving to the transaction hash of the fund transfer 13 | */ 14 | async function requestLinkFromFaucet(linkAddress, to, amount) { 15 | const LINK_FAUCET_ADDRESS = `0x4281eCF07378Ee595C564a59048801330f3084eE`; 16 | const linkFaucetImpersonated = await ethers.getImpersonatedSigner(LINK_FAUCET_ADDRESS); 17 | 18 | const linkToken = new ethers.Contract(linkAddress, LinkTokenAbi, ethers.provider); 19 | const tx = await linkToken.connect(linkFaucetImpersonated).transfer(to, amount); 20 | 21 | return tx.hash; 22 | } 23 | 24 | /** 25 | * Requests native coins from the faucet 26 | * 27 | * @param {string} to The address to send coins to 28 | * @param {bigint} amount The amount of coins to request 29 | */ 30 | async function requestNativeFromFaucet(to, amount) { 31 | await setBalance(to, amount); 32 | } 33 | 34 | module.exports = { 35 | requestLinkFromFaucet, 36 | requestNativeFromFaucet 37 | }; -------------------------------------------------------------------------------- /scripts/data-streams/ReportVersions.js: -------------------------------------------------------------------------------- 1 | class ReportV2 { 2 | constructor({ 3 | feedId, 4 | validFromTimestamp, 5 | observationsTimestamp, 6 | nativeFee, 7 | linkFee, 8 | expiresAt, 9 | benchmarkPrice, 10 | }) { 11 | this.feedId = feedId; // (bytes32) The feed ID the report has data for 12 | this.validFromTimestamp = validFromTimestamp; // (uint32) Earliest timestamp for which price is applicable 13 | this.observationsTimestamp = observationsTimestamp; // (uint32) Latest timestamp for which price is applicable 14 | this.nativeFee = nativeFee; // (uint192) Base cost to validate a transaction using the report, denominated in the chain’s native token (WETH/ETH) 15 | this.linkFee = linkFee; // (uint192) Base cost to validate a transaction using the report, denominated in LINK 16 | this.expiresAt = expiresAt; // (uint32) Latest timestamp where the report can be verified on-chain 17 | this.benchmarkPrice = benchmarkPrice; // (int192) DON consensus median price, carried to 8 decimal places 18 | } 19 | } 20 | 21 | class ReportV3 { 22 | constructor({ 23 | feedId, 24 | validFromTimestamp, 25 | observationsTimestamp, 26 | nativeFee, 27 | linkFee, 28 | expiresAt, 29 | price, 30 | bid, 31 | ask, 32 | }) { 33 | this.feedId = feedId; // (bytes32) The stream ID the report has data for 34 | this.validFromTimestamp = validFromTimestamp; // (uint32) Earliest timestamp for which price is applicable 35 | this.observationsTimestamp = observationsTimestamp; // (uint32) Latest timestamp for which price is applicable 36 | this.nativeFee = nativeFee; // (uint192) Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH) 37 | this.linkFee = linkFee; // (uint192) Base cost to validate a transaction using the report, denominated in LINK 38 | this.expiresAt = expiresAt; // (uint32) Latest timestamp where the report can be verified on-chain 39 | this.price = price; // (int192) DON consensus median price (8 or 18 decimals) 40 | this.bid = bid; // (int192) Simulated price impact of a buy order up to the X% depth of liquidity utilisation (8 or 18 decimals) 41 | this.ask = ask; // (int192) Simulated price impact of a sell order up to the X% depth of liquidity utilisation (8 or 18 decimals) 42 | } 43 | } 44 | 45 | class ReportV4 { 46 | constructor({ 47 | feedId, 48 | validFromTimestamp, 49 | observationsTimestamp, 50 | nativeFee, 51 | linkFee, 52 | expiresAt, 53 | price, 54 | marketStatus, 55 | }) { 56 | this.feedId = feedId; // (bytes32) The stream ID the report has data for 57 | this.validFromTimestamp = validFromTimestamp; // (uint32) Earliest timestamp for which price is applicable 58 | this.observationsTimestamp = observationsTimestamp; // (uint32) Latest timestamp for which price is applicable 59 | this.nativeFee = nativeFee; // (uint192) Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH) 60 | this.linkFee = linkFee; // (uint192) Base cost to validate a transaction using the report, denominated in LINK 61 | this.expiresAt = expiresAt; // (uint32) Latest timestamp where the report can be verified on-chain 62 | this.price = price; // (int192) DON consensus median benchmark price (8 or 18 decimals) 63 | this.marketStatus = marketStatus; // (uint32) The DON's consensus on whether the market is currently open 64 | } 65 | } 66 | 67 | module.exports = { 68 | ReportV2, 69 | ReportV3, 70 | ReportV4 71 | } -------------------------------------------------------------------------------- /scripts/examples/DataStreamsConsumerFork.ts: -------------------------------------------------------------------------------- 1 | import { ethers, network } from "hardhat"; 2 | import { requestLinkFromFaucet, requestNativeFromFaucet } from "../data-streams/DataStreamsLocalSimulatorFork"; 3 | 4 | // 1st Terminal: npx hardhat node 5 | // 2nd Terminal: npx hardhat run ./scripts/examples/DataStreamsConsumerFork.ts --network localhost 6 | 7 | async function main() { 8 | const verifierProxyInterface = new ethers.Interface([ 9 | "function verify(bytes calldata payload, bytes calldata parameterPayload) external payable returns (bytes memory verifierResponse)", 10 | "function s_feeManager() external view returns (address)" 11 | ]); 12 | 13 | const feeManagerInterface = new ethers.Interface([ 14 | "function getFeeAndReward(address subscriber, bytes memory unverifiedReport, address quoteAddress) external returns ((address token,uint256 amount) memory, (address token,uint256 amount) memory, uint256)", 15 | "function i_linkAddress() external view returns (address)", 16 | "function i_nativeAddress() external view returns (address)", 17 | "function i_rewardManager() external view returns (address)" 18 | ]); 19 | 20 | const erc20Interface = new ethers.Interface(["function approve(address spender, uint256 amount) external returns (bool)"]); 21 | 22 | const ARBITRUM_SEPOLIA_RPC_URL = process.env.ARBITRUM_SEPOLIA_RPC_URL; 23 | 24 | await network.provider.request({ 25 | method: "hardhat_reset", 26 | params: [{ 27 | forking: { 28 | jsonRpcUrl: ARBITRUM_SEPOLIA_RPC_URL, 29 | blockNumber: 99556570 30 | }, 31 | }], 32 | }); 33 | 34 | const [alice] = await ethers.getSigners(); 35 | 36 | const UNVERIFIED_INPUT_REPORT = "0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000004b0b4006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000067407f400000000000000000000000000000000000000000000000000000000067407f4000000000000000000000000000000000000000000000000000001b1fa2a2d6400000000000000000000000000000000000000000000000000016e76dd08b2c34000000000000000000000000000000000000000000000000000000006741d0c00000000000000000000000000000000000000000000000b5c654dd994866cb600000000000000000000000000000000000000000000000b5c6042e4f23a92c400000000000000000000000000000000000000000000000b5c7dc7c8e30df80000000000000000000000000000000000000000000000000000000000000000002f68eeb7e489bef244eb83d56e1b8af210a65363e5ad4407f374e5c06f46db98c36b4181b70329535011ba504fb8e09570c10a6de7f1f138cf322237c72cb0d650000000000000000000000000000000000000000000000000000000000000002648579956ffb863e4ea9470a87bb59b26e91ea893f96c2ab933786e531f2701a06d92b4e896a42d40d19a3b5ba1711d5f00bc262084d7afce44a5bbde3014fe5"; 37 | 38 | const EXPECTED_REPORT_DATA = { 39 | feedId: "0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782", 40 | validFromTimestamp: 1732280128, 41 | observationsTimestamp: 1732280128, 42 | nativeFee: 29822686516800, 43 | linkFee: 6446908323867700, 44 | expiresAt: 1732366528, 45 | price: 3353151968509396700000n, 46 | bid: 3353129257778281000000n, 47 | ask: 3353262200000000000000n 48 | }; 49 | 50 | const verifierProxyAddress = `0x2ff010DEbC1297f19579B4246cad07bd24F2488A`; 51 | const verifierProxy = new ethers.Contract(verifierProxyAddress, verifierProxyInterface, alice); 52 | 53 | const feeManagerAddress = await verifierProxy.s_feeManager(); 54 | const feeManager = new ethers.Contract(feeManagerAddress, feeManagerInterface, alice); 55 | 56 | const rewardManagerAddress = await feeManager.i_rewardManager(); 57 | 58 | const defaultAbiCoder = ethers.AbiCoder.defaultAbiCoder(); 59 | 60 | const [, reportData] = defaultAbiCoder.decode(["bytes32[3]", "bytes"], UNVERIFIED_INPUT_REPORT); 61 | 62 | console.log(reportData); 63 | 64 | // Pay for verification in LINK 65 | let feeTokenAddress = await feeManager.i_linkAddress(); 66 | 67 | await requestLinkFromFaucet(feeTokenAddress, alice.address, EXPECTED_REPORT_DATA.linkFee); 68 | 69 | const linkToken = new ethers.Contract(feeTokenAddress, erc20Interface, alice); 70 | await linkToken.approve(rewardManagerAddress, EXPECTED_REPORT_DATA.linkFee); 71 | 72 | let parameterPayload = defaultAbiCoder.encode(["address"], [feeTokenAddress]); 73 | 74 | await verifierProxy.verify(UNVERIFIED_INPUT_REPORT, parameterPayload); // this must not revert 75 | 76 | // Pay for verification in Native 77 | feeTokenAddress = await feeManager.i_nativeAddress(); 78 | const gasCosts = ethers.parseEther("0.1"); 79 | await requestNativeFromFaucet(alice.address, EXPECTED_REPORT_DATA.nativeFee + Number(gasCosts)); 80 | 81 | parameterPayload = defaultAbiCoder.encode(["address"], [feeTokenAddress]); 82 | 83 | await verifierProxy.verify(UNVERIFIED_INPUT_REPORT, parameterPayload, { value: EXPECTED_REPORT_DATA.nativeFee }); // this must not revert 84 | 85 | // const txData = verifierProxy.interface.encodeFunctionData("verify", [UNVERIFIED_INPUT_REPORT, parameterPayload]); 86 | // const txResult = await alice.call({ 87 | // to: verifierProxyAddress, 88 | // data: txData, 89 | // }); 90 | // console.log(txResult); 91 | } 92 | 93 | 94 | main().catch((error) => { 95 | console.error(error); 96 | process.exitCode = 1; 97 | }); 98 | -------------------------------------------------------------------------------- /scripts/examples/UnsafeTokenAndDataTransfer.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | // npx hardhat run ./scripts/examples/UnsafeTokenAndDataTransfer.ts 4 | 5 | async function main() { 6 | const localSimulatorFactory = await ethers.getContractFactory("CCIPLocalSimulator"); 7 | const localSimulator = await localSimulatorFactory.deploy(); 8 | 9 | const config: { 10 | chainSelector_: bigint; 11 | sourceRouter_: string; 12 | destinationRouter_: string; 13 | wrappedNative_: string; 14 | linkToken_: string; 15 | ccipBnM_: string; 16 | ccipLnM_: string; 17 | } = await localSimulator.configuration(); 18 | 19 | const CCIPSender_UnsafeFactory = await ethers.getContractFactory("CCIPSender_Unsafe"); 20 | const CCIPSender_Unsafe = await CCIPSender_UnsafeFactory.deploy(config.linkToken_, config.sourceRouter_); 21 | 22 | console.log("Deployed CCIPSender_Unsafe to: ", CCIPSender_Unsafe.target); 23 | 24 | const CCIPReceiver_UnsafeFactory = await ethers.getContractFactory("CCIPReceiver_Unsafe"); 25 | const CCIPReceiver_Unsafe = await CCIPReceiver_UnsafeFactory.deploy(config.destinationRouter_); 26 | 27 | console.log("Deployed CCIPReceiver_Unsafe to: ", CCIPReceiver_Unsafe.target); 28 | 29 | console.log("-------------------------------------------") 30 | 31 | const ccipBnMFactory = await ethers.getContractFactory("BurnMintERC677Helper"); 32 | const ccipBnM = ccipBnMFactory.attach(config.ccipBnM_); 33 | 34 | await ccipBnM.drip(CCIPSender_Unsafe.target); 35 | 36 | const textToSend = `Hello World`; 37 | const amountToSend = 100; 38 | 39 | console.log(`Balance of CCIPSender_Unsafe before: `, await ccipBnM.balanceOf(CCIPSender_Unsafe.target)); 40 | console.log(`Balance of CCIPReceiver_Unsafe before: `, await ccipBnM.balanceOf(CCIPReceiver_Unsafe.target)); 41 | console.log("-------------------------------------------") 42 | 43 | const tx = await CCIPSender_Unsafe.send(CCIPReceiver_Unsafe.target, textToSend, config.chainSelector_, config.ccipBnM_, amountToSend); 44 | console.log("Transaction hash: ", tx.hash); 45 | 46 | console.log("-------------------------------------------") 47 | console.log(`Balance of CCIPSender_Unsafe after: `, await ccipBnM.balanceOf(CCIPSender_Unsafe.target)); 48 | console.log(`Balance of CCIPReceiver_Unsafe after: `, await ccipBnM.balanceOf(CCIPReceiver_Unsafe.target)); 49 | 50 | console.log("-------------------------------------------") 51 | const received = await CCIPReceiver_Unsafe.text(); 52 | console.log(`Received:`, received); 53 | } 54 | 55 | // We recommend this pattern to be able to use async/await everywhere 56 | // and properly handle errors. 57 | main().catch((error) => { 58 | console.error(error); 59 | process.exitCode = 1; 60 | }); 61 | -------------------------------------------------------------------------------- /scripts/examples/UnsafeTokenAndDataTransferFork.ts: -------------------------------------------------------------------------------- 1 | import { ethers, network } from "hardhat"; 2 | import { getEvm2EvmMessage, requestLinkFromTheFaucet, routeMessage } from "../CCIPLocalSimulatorFork"; 3 | 4 | // 1st Terminal: npx hardhat node 5 | // 2nd Terminal: npx hardhat run ./scripts/examples/UnsafeTokenAndDataTransferFork.ts --network localhost 6 | 7 | async function main() { 8 | const ETHEREUM_SEPOLIA_RPC_URL = process.env.ETHEREUM_SEPOLIA_RPC_URL; 9 | const ARBITRUM_SEPOLIA_RPC_URL = process.env.ARBITRUM_SEPOLIA_RPC_URL; 10 | 11 | await network.provider.request({ 12 | method: "hardhat_reset", 13 | params: [{ 14 | forking: { 15 | jsonRpcUrl: ARBITRUM_SEPOLIA_RPC_URL, 16 | blockNumber: 33079804 17 | }, 18 | }], 19 | }); 20 | 21 | const ccipRouterAddressArbSepolia = `0x2a9C5afB0d0e4BAb2BCdaE109EC4b0c4Be15a165`; 22 | const ccipBnMTokenAddressArbSepolia = `0xA8C0c11bf64AF62CDCA6f93D3769B88BdD7cb93D` 23 | 24 | const CCIPReceiver_UnsafeFactory = await ethers.getContractFactory("CCIPReceiver_Unsafe"); 25 | let CCIPReceiver_Unsafe = await CCIPReceiver_UnsafeFactory.deploy(ccipRouterAddressArbSepolia); 26 | 27 | console.log("Deployed CCIPReceiver_Unsafe to: ", CCIPReceiver_Unsafe.target); 28 | 29 | const ccipBnMFactory = await ethers.getContractFactory("BurnMintERC677Helper"); 30 | const ccipBnMArbSepolia = ccipBnMFactory.attach(ccipBnMTokenAddressArbSepolia); 31 | 32 | console.log(`Balance of CCIPReceiver_Unsafe before: `, await ccipBnMArbSepolia.balanceOf(CCIPReceiver_Unsafe.target)); 33 | 34 | console.log("-------------------------------------------"); 35 | 36 | 37 | await network.provider.request({ 38 | method: "hardhat_reset", 39 | params: [{ 40 | forking: { 41 | jsonRpcUrl: ETHEREUM_SEPOLIA_RPC_URL, 42 | blockNumber: 5663645 43 | }, 44 | }], 45 | }); 46 | 47 | const linkTokenAddressSepolia = `0x779877A7B0D9E8603169DdbD7836e478b4624789`; 48 | const ccipRouterAddressSepolia = `0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59`; 49 | const ccipBnMTokenAddressSepolia = `0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05`; 50 | 51 | const CCIPSender_UnsafeFactory = await ethers.getContractFactory("CCIPSender_Unsafe"); 52 | const CCIPSender_Unsafe = await CCIPSender_UnsafeFactory.deploy(linkTokenAddressSepolia, ccipRouterAddressSepolia); 53 | 54 | console.log("Deployed CCIPSender_Unsafe to: ", CCIPSender_Unsafe.target); 55 | 56 | const ccipBnMSepolia = ccipBnMFactory.attach(ccipBnMTokenAddressSepolia); 57 | 58 | await ccipBnMSepolia.drip(CCIPSender_Unsafe.target); 59 | 60 | const linkAmountForFees = 5000000000000000000n; // 5 LINK 61 | await requestLinkFromTheFaucet(linkTokenAddressSepolia, await CCIPSender_Unsafe.getAddress(), linkAmountForFees); 62 | 63 | const textToSend = `Hello World`; 64 | const amountToSend = 100; 65 | const arbSepoliaChainSelector = 3478487238524512106n; 66 | 67 | console.log(`Balance of CCIPSender_Unsafe before: `, await ccipBnMSepolia.balanceOf(CCIPSender_Unsafe.target)); 68 | 69 | const tx = await CCIPSender_Unsafe.send(CCIPReceiver_Unsafe.target, textToSend, arbSepoliaChainSelector, ccipBnMTokenAddressSepolia, amountToSend); 70 | console.log("Transaction hash: ", tx.hash); 71 | const receipt = await tx.wait(); 72 | if (!receipt) return; 73 | const evm2EvmMessage = getEvm2EvmMessage(receipt); 74 | 75 | console.log(`Balance of CCIPSender_Unsafe after: `, await ccipBnMSepolia.balanceOf(CCIPSender_Unsafe.target)); 76 | 77 | console.log("-------------------------------------------"); 78 | 79 | await network.provider.request({ 80 | method: "hardhat_reset", 81 | params: [{ 82 | forking: { 83 | jsonRpcUrl: ARBITRUM_SEPOLIA_RPC_URL, 84 | blockNumber: 33079804 85 | }, 86 | }], 87 | }); 88 | 89 | // We must redeploy it because of the network reset but it will be deployed to the same address because of the CREATE opcode: address = keccak256(rlp([sender_address,sender_nonce]))[12:] 90 | CCIPReceiver_Unsafe = await CCIPReceiver_UnsafeFactory.deploy(ccipRouterAddressArbSepolia); 91 | 92 | if (!evm2EvmMessage) return; 93 | await routeMessage(ccipRouterAddressArbSepolia, evm2EvmMessage); 94 | 95 | const received = await CCIPReceiver_Unsafe.text(); 96 | console.log(`Received:`, received); 97 | 98 | console.log(`Balance of CCIPReceiver_Unsafe after: `, await ccipBnMArbSepolia.balanceOf(CCIPReceiver_Unsafe.target)); 99 | } 100 | 101 | // We recommend this pattern to be able to use async/await everywhere 102 | // and properly handle errors. 103 | main().catch((error) => { 104 | console.error(error); 105 | process.exitCode = 1; 106 | }); 107 | -------------------------------------------------------------------------------- /src/ccip/BurnMintERC677Helper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.19; 3 | 4 | import {BurnMintERC677} from "@chainlink/contracts-ccip/src/v0.8/shared/token/ERC677/BurnMintERC677.sol"; 5 | 6 | /// @title BurnMintERC677Helper 7 | /// @notice This contract extends the functionality of the BurnMintERC677 token contract to include a `drip` function that mints one full token to a specified address. 8 | /// @dev Inherits from the BurnMintERC677 contract and sets the token name, symbol, decimals, and initial supply in the constructor. 9 | contract BurnMintERC677Helper is BurnMintERC677 { 10 | /** 11 | * @notice Constructor to initialize the BurnMintERC677Helper contract with a name and symbol. 12 | * @dev Calls the parent constructor of BurnMintERC677 with fixed decimals (18) and initial supply (0). 13 | * @param name - The name of the token. 14 | * @param symbol - The symbol of the token. 15 | */ 16 | constructor( 17 | string memory name, 18 | string memory symbol 19 | ) BurnMintERC677(name, symbol, 18, 0) {} 20 | 21 | /** 22 | * @notice Mints one full token (1e18) to the specified address. 23 | * @dev Calls the internal `_mint` function from the BurnMintERC677 contract. 24 | * @param to - The address to receive the minted token. 25 | */ 26 | function drip(address to) external { 27 | _mint(to, 1e18); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/data-feeds/MockOffchainAggregator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title MockOffchainAggregator 5 | /// @notice This contract is a mock implementation of an offchain aggregator for testing purposes. 6 | /// @dev This contract simulates the behavior of an offchain aggregator and allows for updating answers and round data. 7 | contract MockOffchainAggregator { 8 | /// @notice The minimum possible answer the aggregator can report. 9 | int192 private constant MIN_ANSWER_POSSIBLE = 1; 10 | 11 | /// @notice The maximum possible answer the aggregator can report. 12 | int192 private constant MAX_ANSWER_POSSIBLE = 95780971304118053647396689196894323976171195136475135; // type(uint176).max 13 | 14 | /// @notice The number of decimals used by the aggregator. 15 | uint8 public decimals; 16 | 17 | /// @notice The latest answer reported by the aggregator. 18 | int256 public latestAnswer; 19 | 20 | /// @notice The timestamp of the latest answer. 21 | uint256 public latestTimestamp; 22 | 23 | /// @notice The latest round ID. 24 | uint256 public latestRound; 25 | 26 | /// @notice The minimum answer the aggregator is allowed to report. 27 | int192 public minAnswer; 28 | 29 | /// @notice The maximum answer the aggregator is allowed to report. 30 | int192 public maxAnswer; 31 | 32 | /// @notice Mapping to get the answer for a specific round ID. 33 | mapping(uint256 => int256) public getAnswer; 34 | 35 | /// @notice Mapping to get the timestamp for a specific round ID. 36 | mapping(uint256 => uint256) public getTimestamp; 37 | 38 | /// @notice Mapping to get the start time for a specific round ID. 39 | mapping(uint256 => uint256) private getStartedAt; 40 | 41 | /** 42 | * @notice Constructor to initialize the MockOffchainAggregator contract with initial parameters. 43 | * @param _decimals - The number of decimals for the aggregator. 44 | * @param _initialAnswer - The initial answer to be set in the mock aggregator. 45 | */ 46 | constructor(uint8 _decimals, int256 _initialAnswer) { 47 | decimals = _decimals; 48 | updateAnswer(_initialAnswer); 49 | minAnswer = MIN_ANSWER_POSSIBLE; 50 | maxAnswer = MAX_ANSWER_POSSIBLE; 51 | } 52 | 53 | /** 54 | * @notice Updates the answer in the mock aggregator. 55 | * @param _answer - The new answer to be set. 56 | */ 57 | function updateAnswer(int256 _answer) public { 58 | latestAnswer = _answer; 59 | latestTimestamp = block.timestamp; 60 | latestRound++; 61 | getAnswer[latestRound] = _answer; 62 | getTimestamp[latestRound] = block.timestamp; 63 | getStartedAt[latestRound] = block.timestamp; 64 | } 65 | 66 | /** 67 | * @notice Updates the round data in the mock aggregator. 68 | * @param _roundId - The round ID to be updated. 69 | * @param _answer - The new answer to be set. 70 | * @param _timestamp - The timestamp to be set. 71 | * @param _startedAt - The timestamp when the round started. 72 | */ 73 | function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public { 74 | latestRound = _roundId; 75 | latestAnswer = _answer; 76 | latestTimestamp = _timestamp; 77 | getAnswer[latestRound] = _answer; 78 | getTimestamp[latestRound] = _timestamp; 79 | getStartedAt[latestRound] = _startedAt; 80 | } 81 | 82 | /** 83 | * @notice Gets the round data for a specific round ID. 84 | * @param _roundId - The round ID to get the data for. 85 | * @return roundId - The round ID. 86 | * @return answer - The answer for the round. 87 | * @return startedAt - The timestamp when the round started. 88 | * @return updatedAt - The timestamp when the round was updated. 89 | * @return answeredInRound - The round ID in which the answer was computed. 90 | */ 91 | function getRoundData(uint80 _roundId) 92 | external 93 | view 94 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 95 | { 96 | return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId); 97 | } 98 | 99 | /** 100 | * @notice Gets the latest round data. 101 | * @return roundId - The latest round ID. 102 | * @return answer - The latest answer. 103 | * @return startedAt - The timestamp when the latest round started. 104 | * @return updatedAt - The timestamp when the latest round was updated. 105 | * @return answeredInRound - The round ID in which the latest answer was computed. 106 | */ 107 | function latestRoundData() 108 | external 109 | view 110 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 111 | { 112 | return ( 113 | uint80(latestRound), 114 | getAnswer[latestRound], 115 | getStartedAt[latestRound], 116 | getTimestamp[latestRound], 117 | uint80(latestRound) 118 | ); 119 | } 120 | 121 | /** 122 | * @notice Updates the minimum and maximum answers the aggregator can report. 123 | * @param _minAnswer - The new minimum answer. 124 | * @param _maxAnswer - The new maximum answer. 125 | */ 126 | function updateMinAndMaxAnswers(int192 _minAnswer, int192 _maxAnswer) external { 127 | require(_minAnswer < _maxAnswer, "minAnswer must be less than maxAnswer"); 128 | require(_minAnswer >= MIN_ANSWER_POSSIBLE, "minAnswer is too low"); 129 | require(_maxAnswer <= MAX_ANSWER_POSSIBLE, "maxAnswer is too high"); 130 | 131 | minAnswer = _minAnswer; 132 | maxAnswer = _maxAnswer; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/data-feeds/MockV3Aggregator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { 5 | AggregatorInterface, 6 | AggregatorV3Interface, 7 | AggregatorV2V3Interface 8 | } from "./interfaces/AggregatorV2V3Interface.sol"; 9 | import {MockOffchainAggregator} from "./MockOffchainAggregator.sol"; 10 | 11 | /// @title MockV3Aggregator 12 | /// @notice This contract is a mock implementation of the AggregatorV2V3Interface for testing purposes. 13 | /// @dev This contract interacts with a MockOffchainAggregator to simulate price feeds. 14 | contract MockV3Aggregator is AggregatorV2V3Interface { 15 | /// @notice The version of the aggregator. 16 | uint256 public constant override version = 0; 17 | 18 | /// @notice The address of the current aggregator. 19 | address public aggregator; 20 | 21 | /// @notice The address of the proposed aggregator. 22 | address public proposedAggregator; 23 | 24 | /** 25 | * @notice Constructor to initialize the MockV3Aggregator contract with initial parameters. 26 | * @param _decimals - The number of decimals for the aggregator. 27 | * @param _initialAnswer - The initial answer to be set in the mock aggregator. 28 | */ 29 | constructor(uint8 _decimals, int256 _initialAnswer) { 30 | aggregator = address(new MockOffchainAggregator(_decimals, _initialAnswer)); 31 | proposedAggregator = address(0); 32 | } 33 | 34 | /** 35 | * @inheritdoc AggregatorV3Interface 36 | */ 37 | function decimals() external view override returns (uint8) { 38 | return AggregatorV2V3Interface(aggregator).decimals(); 39 | } 40 | 41 | /** 42 | * @inheritdoc AggregatorInterface 43 | */ 44 | function getAnswer(uint256 roundId) external view override returns (int256) { 45 | return AggregatorV2V3Interface(aggregator).getAnswer(roundId); 46 | } 47 | 48 | /** 49 | * @inheritdoc AggregatorV3Interface 50 | */ 51 | function getRoundData(uint80 _roundId) 52 | external 53 | view 54 | override 55 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 56 | { 57 | return AggregatorV2V3Interface(aggregator).getRoundData(_roundId); 58 | } 59 | 60 | /** 61 | * @inheritdoc AggregatorV3Interface 62 | */ 63 | function latestRoundData() 64 | external 65 | view 66 | override 67 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 68 | { 69 | return AggregatorV2V3Interface(aggregator).latestRoundData(); 70 | } 71 | 72 | /** 73 | * @inheritdoc AggregatorInterface 74 | */ 75 | function getTimestamp(uint256 roundId) external view override returns (uint256) { 76 | return AggregatorV2V3Interface(aggregator).getTimestamp(roundId); 77 | } 78 | 79 | /** 80 | * @inheritdoc AggregatorInterface 81 | */ 82 | function latestAnswer() external view override returns (int256) { 83 | return AggregatorV2V3Interface(aggregator).latestAnswer(); 84 | } 85 | 86 | /** 87 | * @inheritdoc AggregatorInterface 88 | */ 89 | function latestTimestamp() external view override returns (uint256) { 90 | return AggregatorV2V3Interface(aggregator).latestTimestamp(); 91 | } 92 | 93 | /** 94 | * @inheritdoc AggregatorInterface 95 | */ 96 | function latestRound() external view override returns (uint256) { 97 | return AggregatorV2V3Interface(aggregator).latestRound(); 98 | } 99 | 100 | /** 101 | * @notice Updates the answer in the mock aggregator. 102 | * @param _answer - The new answer to be set. 103 | */ 104 | function updateAnswer(int256 _answer) public { 105 | MockOffchainAggregator(aggregator).updateAnswer(_answer); 106 | } 107 | 108 | /** 109 | * @notice Updates the round data in the mock aggregator. 110 | * @param _roundId - The round ID to be updated. 111 | * @param _answer - The new answer to be set. 112 | * @param _timestamp - The timestamp to be set. 113 | * @param _startedAt - The timestamp when the round started. 114 | */ 115 | function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public { 116 | MockOffchainAggregator(aggregator).updateRoundData(_roundId, _answer, _timestamp, _startedAt); 117 | } 118 | 119 | /** 120 | * @notice Proposes a new aggregator. 121 | * @param _aggregator - The address of the proposed aggregator. 122 | */ 123 | function proposeAggregator(AggregatorV2V3Interface _aggregator) external { 124 | require(address(_aggregator) != address(0), "Proposed aggregator cannot be zero address"); 125 | require(address(_aggregator) != aggregator, "Proposed aggregator cannot be current aggregator"); 126 | proposedAggregator = address(_aggregator); 127 | } 128 | 129 | /** 130 | * @notice Confirms the proposed aggregator. 131 | * @param _aggregator - The address of the proposed aggregator. 132 | */ 133 | function confirmAggregator(address _aggregator) external { 134 | require(_aggregator == address(proposedAggregator), "Invalid proposed aggregator"); 135 | aggregator = proposedAggregator; 136 | proposedAggregator = address(0); 137 | } 138 | 139 | /** 140 | * @inheritdoc AggregatorV3Interface 141 | */ 142 | function description() external pure override returns (string memory) { 143 | return "src/data-feeds/MockV3Aggregator.sol"; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/data-feeds/interfaces/AggregatorInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title AggregatorInterface 5 | /// @notice Interface for accessing data from an aggregator contract. 6 | /// @dev Provides methods to get the latest data and historical data for specific rounds. 7 | interface AggregatorInterface { 8 | /** 9 | * @notice Gets the latest answer from the aggregator. 10 | * @return int256 - The latest answer. 11 | */ 12 | function latestAnswer() external view returns (int256); 13 | 14 | /** 15 | * @notice Gets the timestamp of the latest answer from the aggregator. 16 | * @return uint256 - The timestamp of the latest answer. 17 | */ 18 | function latestTimestamp() external view returns (uint256); 19 | 20 | /** 21 | * @notice Gets the latest round ID from the aggregator. 22 | * @return uint256 - The latest round ID. 23 | */ 24 | function latestRound() external view returns (uint256); 25 | 26 | /** 27 | * @notice Gets the answer for a specific round ID. 28 | * @param roundId - The round ID to get the answer for. 29 | * @return int256 - The answer for the given round ID. 30 | */ 31 | function getAnswer(uint256 roundId) external view returns (int256); 32 | 33 | /** 34 | * @notice Gets the timestamp for a specific round ID. 35 | * @param roundId - The round ID to get the timestamp for. 36 | * @return uint256 - The timestamp for the given round ID. 37 | */ 38 | function getTimestamp(uint256 roundId) external view returns (uint256); 39 | 40 | /** 41 | * @notice Emitted when the answer is updated. 42 | * @param current - The updated answer. 43 | * @param roundId - The round ID for which the answer was updated. 44 | * @param updatedAt - The timestamp when the answer was updated. 45 | */ 46 | event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); 47 | 48 | /** 49 | * @notice Emitted when a new round is started. 50 | * @param roundId - The round ID of the new round. 51 | * @param startedBy - The address of the account that started the round. 52 | * @param startedAt - The timestamp when the round was started. 53 | */ 54 | event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt); 55 | } 56 | -------------------------------------------------------------------------------- /src/data-feeds/interfaces/AggregatorV2V3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {AggregatorInterface} from "./AggregatorInterface.sol"; 5 | import {AggregatorV3Interface} from "./AggregatorV3Interface.sol"; 6 | 7 | /// @title AggregatorV2V3Interface 8 | /// @notice Interface that inherits from both AggregatorInterface and AggregatorV3Interface. 9 | interface AggregatorV2V3Interface is AggregatorInterface, AggregatorV3Interface {} 10 | -------------------------------------------------------------------------------- /src/data-feeds/interfaces/AggregatorV3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title AggregatorV3Interface 5 | /// @notice Interface for accessing detailed data from an aggregator contract, including round data and metadata. 6 | /// @dev Provides methods to get the latest data, historical data for specific rounds, and metadata such as decimals and description. 7 | interface AggregatorV3Interface { 8 | /** 9 | * @notice Gets the number of decimals used by the aggregator. 10 | * @return uint8 - The number of decimals. 11 | */ 12 | function decimals() external view returns (uint8); 13 | 14 | /** 15 | * @notice Gets the description of the aggregator. 16 | * @return string memory - The description of the aggregator. 17 | */ 18 | function description() external view returns (string memory); 19 | 20 | /** 21 | * @notice Gets the version of the aggregator. 22 | * @return uint256 - The version of the aggregator. 23 | */ 24 | function version() external view returns (uint256); 25 | 26 | /** 27 | * @notice Gets the round data for a specific round ID. 28 | * @param _roundId - The round ID to get the data for. 29 | * @return roundId - The round ID. 30 | * @return answer - The answer for the round. 31 | * @return startedAt - The timestamp when the round started. 32 | * @return updatedAt - The timestamp when the round was updated. 33 | * @return answeredInRound - The round ID in which the answer was computed. 34 | * @dev This function should raise "No data present" if no data is available for the given round ID. 35 | */ 36 | function getRoundData(uint80 _roundId) 37 | external 38 | view 39 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); 40 | 41 | /** 42 | * @notice Gets the latest round data. 43 | * @return roundId - The latest round ID. 44 | * @return answer - The latest answer. 45 | * @return startedAt - The timestamp when the latest round started. 46 | * @return updatedAt - The timestamp when the latest round was updated. 47 | * @return answeredInRound - The round ID in which the latest answer was computed. 48 | * @dev This function should raise "No data present" if no data is available. 49 | */ 50 | function latestRoundData() 51 | external 52 | view 53 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); 54 | } 55 | -------------------------------------------------------------------------------- /src/data-streams/DataStreamsLocalSimulator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {WETH9} from "../shared/WETH9.sol"; 5 | import {LinkToken} from "../shared/LinkToken.sol"; 6 | import {MockVerifier} from "./MockVerifier.sol"; 7 | import {MockVerifierProxy} from "./MockVerifierProxy.sol"; 8 | import {MockFeeManager} from "./MockFeeManager.sol"; 9 | import {MockRewardManager} from "./MockRewardManager.sol"; 10 | 11 | contract DataStreamsLocalSimulator { 12 | MockVerifier internal s_mockVerifier; 13 | MockVerifierProxy internal s_mockVerifierProxy; 14 | MockFeeManager internal s_mockFeeManager; 15 | MockRewardManager internal s_mockRewardManager; 16 | 17 | /// @notice The wrapped native token instance 18 | WETH9 internal immutable i_wrappedNative; 19 | 20 | /// @notice The LINK token instance 21 | LinkToken internal immutable i_linkToken; 22 | 23 | constructor() { 24 | i_wrappedNative = new WETH9(); 25 | i_linkToken = new LinkToken(); 26 | 27 | s_mockVerifierProxy = new MockVerifierProxy(); 28 | s_mockVerifier = new MockVerifier(address(s_mockVerifierProxy)); 29 | s_mockVerifierProxy.initializeVerifier(address(s_mockVerifier)); 30 | 31 | s_mockRewardManager = new MockRewardManager(address(i_linkToken)); 32 | 33 | s_mockFeeManager = new MockFeeManager( 34 | address(i_linkToken), address(i_wrappedNative), address(s_mockVerifierProxy), address(s_mockRewardManager) 35 | ); 36 | 37 | s_mockVerifierProxy.setFeeManager(s_mockFeeManager); 38 | s_mockRewardManager.setFeeManager(address(s_mockFeeManager)); 39 | } 40 | 41 | /** 42 | * @notice Requests LINK tokens from the faucet. The provided amount of tokens are transferred to provided destination address. 43 | * 44 | * @param to - The address to which LINK tokens are to be sent. 45 | * @param amount - The amount of LINK tokens to send. 46 | * 47 | * @return success - Returns `true` if the transfer of tokens was successful, otherwise `false`. 48 | */ 49 | function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) { 50 | success = i_linkToken.transfer(to, amount); 51 | } 52 | 53 | /** 54 | * @notice Returns configuration details for pre-deployed contracts and services needed for local Data Streams simulations. 55 | * 56 | * @return wrappedNative_ - The wrapped native token. 57 | * @return linkToken_ - The LINK token. 58 | * @return mockVerifier_ - The mock verifier contract. 59 | * @return mockVerifierProxy_ - The mock verifier proxy contract. 60 | * @return mockFeeManager_ - The mock fee manager contract. 61 | * @return mockRewardManager_ - The mock reward manager contract. 62 | */ 63 | function configuration() 64 | public 65 | view 66 | returns ( 67 | WETH9 wrappedNative_, 68 | LinkToken linkToken_, 69 | MockVerifier mockVerifier_, 70 | MockVerifierProxy mockVerifierProxy_, 71 | MockFeeManager mockFeeManager_, 72 | MockRewardManager mockRewardManager_ 73 | ) 74 | { 75 | return 76 | (i_wrappedNative, i_linkToken, s_mockVerifier, s_mockVerifierProxy, s_mockFeeManager, s_mockRewardManager); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/data-streams/DataStreamsLocalSimulatorFork.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, Vm} from "forge-std/Test.sol"; 5 | import {Register} from "./Register.sol"; 6 | import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC20.sol"; 7 | 8 | contract DataStreamsLocalSimulatorFork is Test { 9 | /// @notice The immutable register instance 10 | Register immutable i_register; 11 | 12 | /// @notice The address of the LINK faucet 13 | address constant LINK_FAUCET = 0x4281eCF07378Ee595C564a59048801330f3084eE; 14 | 15 | /** 16 | * @notice Constructor to initialize the contract 17 | */ 18 | constructor() { 19 | i_register = new Register(); 20 | } 21 | 22 | /** 23 | * @notice Returns the default values for currently Data Streams supported networks. If network is not present or some of the values are changed, user can manually add new network details using the `setNetworkDetails` function. 24 | * 25 | * @param chainId - The blockchain network chain ID. For example 11155111 for Ethereum Sepolia. 26 | * 27 | * @return networkDetails - The tuple containing: 28 | * verifierProxyAddress - The address of the Verifier Proxy smart contract. 29 | * linkAddress - The address of the LINK token. 30 | */ 31 | function getNetworkDetails(uint256 chainId) external view returns (Register.NetworkDetails memory) { 32 | return i_register.getNetworkDetails(chainId); 33 | } 34 | 35 | /** 36 | * @notice If network details are not present or some of the values are changed, user can manually add new network details using the `setNetworkDetails` function. 37 | * 38 | * @param chainId - The blockchain network chain ID. For example 11155111 for Ethereum Sepolia. 39 | * @param networkDetails - The tuple containing: 40 | * verifierProxyAddress - The address of the Verifier Proxy smart contract. 41 | * linkAddress - The address of the LINK token. 42 | */ 43 | function setNetworkDetails(uint256 chainId, Register.NetworkDetails memory networkDetails) external { 44 | i_register.setNetworkDetails(chainId, networkDetails); 45 | } 46 | 47 | /** 48 | * @notice Requests LINK tokens from the faucet. The provided amount of tokens are transferred to provided destination address. 49 | * 50 | * @param to - The address to which LINK tokens are to be sent. 51 | * @param amount - The amount of LINK tokens to send. 52 | * 53 | * @return success - Returns `true` if the transfer of tokens was successful, otherwise `false`. 54 | */ 55 | function requestLinkFromFaucet(address to, uint256 amount) external returns (bool success) { 56 | address linkAddress = i_register.getNetworkDetails(block.chainid).linkAddress; 57 | 58 | vm.startPrank(LINK_FAUCET); 59 | success = IERC20(linkAddress).transfer(to, amount); 60 | vm.stopPrank(); 61 | } 62 | 63 | /** 64 | * @notice Requests native coints from the faucet. 65 | * 66 | * @param to - The address to which native coins are to be sent. 67 | * @param amount - The amount of native coins to send. 68 | */ 69 | function requestNativeFromFaucet(address to, uint256 amount) external { 70 | vm.deal(to, amount); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/data-streams/MockFeeManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol"; 5 | import {IERC20} from 6 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 7 | import {SafeERC20} from 8 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; 9 | import {IWERC20} from "@chainlink/contracts/src/v0.8/shared/interfaces/IWERC20.sol"; 10 | import {Math} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/utils/math/Math.sol"; 11 | 12 | // import {IRewardManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol"; 13 | // import {IVerifierFeeManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol"; 14 | import {IRewardManager} from "./interfaces/IRewardManager.sol"; 15 | import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; 16 | 17 | library Common { 18 | // @notice The asset struct to hold the address of an asset and amount 19 | struct Asset { 20 | address assetAddress; 21 | uint256 amount; 22 | } 23 | } 24 | 25 | contract MockFeeManager is IVerifierFeeManager, OwnerIsCreator { 26 | using SafeERC20 for IERC20; 27 | 28 | error Unauthorized(); 29 | error InvalidAddress(); 30 | error InvalidQuote(); 31 | error ExpiredReport(); 32 | error InvalidDiscount(); 33 | error InvalidSurcharge(); 34 | error InvalidDeposit(); 35 | 36 | /// @notice the total discount that can be applied to a fee, 1e18 = 100% discount 37 | uint64 private constant PERCENTAGE_SCALAR = 1e18; 38 | 39 | address public immutable i_linkAddress; 40 | address public immutable i_nativeAddress; 41 | address public immutable i_proxyAddress; 42 | IRewardManager public immutable i_rewardManager; 43 | 44 | uint256 public s_nativeSurcharge; 45 | mapping(address => uint256) public s_mockDiscounts; 46 | 47 | modifier onlyProxy() { 48 | if (msg.sender != i_proxyAddress) revert Unauthorized(); 49 | _; 50 | } 51 | 52 | constructor(address linkAddress, address nativeAddress, address proxyAddress, address rewardManager) { 53 | i_linkAddress = linkAddress; 54 | i_nativeAddress = nativeAddress; 55 | i_proxyAddress = proxyAddress; 56 | i_rewardManager = IRewardManager(rewardManager); 57 | 58 | IERC20(i_linkAddress).approve(address(i_rewardManager), type(uint256).max); 59 | } 60 | 61 | function processFee(bytes calldata payload, bytes calldata parameterPayload, address subscriber) 62 | external 63 | payable 64 | override 65 | onlyProxy 66 | { 67 | _processFee(payload, parameterPayload, subscriber); 68 | } 69 | 70 | function processFeeBulk(bytes[] calldata payloads, bytes calldata parameterPayload, address subscriber) 71 | external 72 | payable 73 | override 74 | onlyProxy 75 | { 76 | for (uint256 i = 0; i < payloads.length; i++) { 77 | _processFee(payloads[i], parameterPayload, subscriber); 78 | } 79 | } 80 | 81 | function getFeeAndReward(address subscriber, bytes memory report, address quoteAddress) 82 | public 83 | view 84 | returns (Common.Asset memory, Common.Asset memory, uint256) 85 | { 86 | Common.Asset memory fee; 87 | Common.Asset memory reward; 88 | 89 | //verify the quote payload is a supported token 90 | if (quoteAddress != i_nativeAddress && quoteAddress != i_linkAddress) { 91 | revert InvalidQuote(); 92 | } 93 | 94 | //decode the report depending on the version 95 | uint256 linkQuantity; 96 | uint256 nativeQuantity; 97 | uint256 expiresAt; 98 | (,,, nativeQuantity, linkQuantity, expiresAt) = 99 | abi.decode(report, (bytes32, uint32, uint32, uint192, uint192, uint32)); 100 | 101 | //read the timestamp bytes from the report data and verify it has not expired 102 | if (expiresAt < block.timestamp) { 103 | revert ExpiredReport(); 104 | } 105 | 106 | uint256 discount = s_mockDiscounts[subscriber]; 107 | 108 | //the reward is always set in LINK 109 | reward.assetAddress = i_linkAddress; 110 | reward.amount = Math.ceilDiv(linkQuantity * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR); 111 | 112 | //calculate either the LINK fee or native fee if it's within the report 113 | if (quoteAddress == i_linkAddress) { 114 | fee.assetAddress = i_linkAddress; 115 | fee.amount = reward.amount; 116 | } else { 117 | uint256 surchargedFee = 118 | Math.ceilDiv(nativeQuantity * (PERCENTAGE_SCALAR + s_nativeSurcharge), PERCENTAGE_SCALAR); 119 | 120 | fee.assetAddress = i_nativeAddress; 121 | fee.amount = Math.ceilDiv(surchargedFee * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR); 122 | } 123 | 124 | return (fee, reward, discount); 125 | } 126 | 127 | function _processFee(bytes calldata payload, bytes calldata parameterPayload, address subscriber) internal { 128 | if (subscriber == address(this)) revert InvalidAddress(); 129 | address quote = abi.decode(parameterPayload, (address)); 130 | 131 | (, bytes memory report) = abi.decode(payload, (bytes32[3], bytes)); 132 | 133 | (Common.Asset memory fee, /*Common.Asset memory reward*/, /*uint256 appliedDiscount*/ ) = 134 | getFeeAndReward(subscriber, report, quote); 135 | 136 | if (fee.assetAddress == i_linkAddress) { 137 | IRewardManager.FeePayment[] memory payments = new IRewardManager.FeePayment[](1); 138 | payments[0] = IRewardManager.FeePayment({poolId: bytes32(0), amount: uint192(fee.amount)}); 139 | i_rewardManager.onFeePaid(payments, subscriber); 140 | } else { 141 | if (msg.value != 0) { 142 | if (fee.amount > msg.value) revert InvalidDeposit(); 143 | 144 | IWERC20(i_nativeAddress).deposit{value: fee.amount}(); 145 | 146 | uint256 change; 147 | unchecked { 148 | change = msg.value - fee.amount; 149 | } 150 | 151 | if (change > 0) { 152 | payable(subscriber).transfer(change); 153 | } 154 | } else { 155 | IERC20(i_nativeAddress).safeTransferFrom(subscriber, address(this), fee.amount); 156 | } 157 | } 158 | } 159 | 160 | function setNativeSurcharge(uint64 surcharge) external { 161 | if (surcharge > PERCENTAGE_SCALAR) revert InvalidSurcharge(); 162 | 163 | s_nativeSurcharge = surcharge; 164 | } 165 | 166 | function setMockDiscount(address subscriber, uint256 discount) external { 167 | if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount(); 168 | 169 | s_mockDiscounts[subscriber] = discount; 170 | } 171 | 172 | function getMockDiscount(address subscriber) external view returns (uint256) { 173 | return s_mockDiscounts[subscriber]; 174 | } 175 | 176 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 177 | return interfaceId == this.processFee.selector || interfaceId == this.processFeeBulk.selector; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/data-streams/MockReportGenerator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {ReportVersions} from "./ReportVersions.sol"; 6 | 7 | contract MockReportGenerator is Test, ReportVersions { 8 | address internal immutable i_donAddress; 9 | uint256 internal immutable i_donDigest; 10 | 11 | bytes32 internal immutable i_reportV2MockFeedId = 12 | hex"0002777777777777777777777777777777777777777777777777777777777777"; 13 | bytes32 internal immutable i_reportV3MockFeedId = 14 | hex"0003777777777777777777777777777777777777777777777777777777777777"; 15 | bytes32 internal immutable i_reportV4MockFeedId = 16 | hex"0004777777777777777777777777777777777777777777777777777777777777"; 17 | 18 | int192 internal s_price; 19 | int192 internal s_bid; 20 | int192 internal s_ask; 21 | uint32 internal s_expiresPeriod; 22 | uint32 internal s_marketStatus; 23 | uint192 internal s_nativeFee; // 0 by default 24 | uint192 internal s_linkFee; // 0 by default 25 | 26 | error MockReportGenerator__InvalidBid(); 27 | error MockReportGenerator__InvalidAsk(); 28 | error MockReportGenerator__CastOverflow(); 29 | 30 | constructor(int192 initialPrice) { 31 | updatePrice(initialPrice); 32 | s_expiresPeriod = 1 days; 33 | s_marketStatus = 2; // 0 (Unknown), 1 (Closed), 2 (Open) 34 | (i_donAddress, i_donDigest) = makeAddrAndKey("Mock Data Streams DON"); 35 | } 36 | 37 | function generateReport(ReportV2 calldata report) external returns (bytes memory signedReport) { 38 | bytes memory reportData = abi.encode(report); 39 | signedReport = signReport(reportData); 40 | } 41 | 42 | function generateReport(ReportV3 calldata report) external returns (bytes memory signedReport) { 43 | bytes memory reportData = abi.encode(report); 44 | signedReport = signReport(reportData); 45 | } 46 | 47 | function generateReport(ReportV4 calldata report) external returns (bytes memory signedReport) { 48 | bytes memory reportData = abi.encode(report); 49 | signedReport = signReport(reportData); 50 | } 51 | 52 | function generateReportV2() external returns (bytes memory signedReport, ReportV2 memory report) { 53 | report = ReportV2({ 54 | feedId: i_reportV2MockFeedId, 55 | validFromTimestamp: toUint32(block.timestamp), 56 | observationsTimestamp: toUint32(block.timestamp), 57 | nativeFee: s_nativeFee, 58 | linkFee: s_linkFee, 59 | expiresAt: toUint32(block.timestamp + s_expiresPeriod), 60 | benchmarkPrice: s_price 61 | }); 62 | bytes memory reportData = abi.encode(report); 63 | signedReport = signReport(reportData); 64 | } 65 | 66 | function generateReportV3() external returns (bytes memory signedReport, ReportV3 memory report) { 67 | report = ReportV3({ 68 | feedId: i_reportV3MockFeedId, 69 | validFromTimestamp: toUint32(block.timestamp), 70 | observationsTimestamp: toUint32(block.timestamp), 71 | nativeFee: s_nativeFee, 72 | linkFee: s_linkFee, 73 | expiresAt: toUint32(block.timestamp + s_expiresPeriod), 74 | price: s_price, 75 | bid: s_bid, 76 | ask: s_ask 77 | }); 78 | bytes memory reportData = abi.encode(report); 79 | signedReport = signReport(reportData); 80 | } 81 | 82 | function generateReportV4() external returns (bytes memory signedReport, ReportV4 memory report) { 83 | report = ReportV4({ 84 | feedId: i_reportV4MockFeedId, 85 | validFromTimestamp: toUint32(block.timestamp), 86 | observationsTimestamp: toUint32(block.timestamp), 87 | nativeFee: s_nativeFee, 88 | linkFee: s_linkFee, 89 | expiresAt: toUint32(block.timestamp + s_expiresPeriod), 90 | price: s_price, 91 | marketStatus: s_marketStatus 92 | }); 93 | bytes memory reportData = abi.encode(report); 94 | signedReport = signReport(reportData); 95 | } 96 | 97 | function updatePrice(int192 price) public { 98 | s_price = price; 99 | int192 delta = price / 1000; // 0.1% = 1/1000 100 | s_bid = price - delta; 101 | s_ask = price + delta; 102 | } 103 | 104 | function updatePriceBidAndAsk(int192 price, int192 bid, int192 ask) external { 105 | // bid < price < ask 106 | if (bid >= price) revert MockReportGenerator__InvalidBid(); 107 | if (ask <= price) revert MockReportGenerator__InvalidAsk(); 108 | 109 | s_price = price; 110 | s_bid = bid; 111 | s_ask = ask; 112 | } 113 | 114 | function updateExpiresPeriod(uint32 period) external { 115 | s_expiresPeriod = period; 116 | } 117 | 118 | function updateMarketStatus(uint32 status) external { 119 | s_marketStatus = status; 120 | } 121 | 122 | function updateFees(uint192 nativeFee, uint192 linkFee) external { 123 | s_nativeFee = nativeFee; 124 | s_linkFee = linkFee; 125 | } 126 | 127 | function getMockDonAddress() external view returns (address) { 128 | return i_donAddress; 129 | } 130 | 131 | function signReport(bytes memory reportData) private returns (bytes memory signedReport) { 132 | bytes32[3] memory reportContext; 133 | bytes32[] memory rawRs = new bytes32[](1); 134 | bytes32[] memory rawSs = new bytes32[](1); 135 | bytes32 rawVs; 136 | 137 | reportContext[0] = bytes32(i_donDigest); 138 | reportContext[1] = ""; // not needed for mocks 139 | reportContext[2] = ""; // not needed for mocks 140 | 141 | vm.startPrank(i_donAddress); 142 | bytes32 hashedReport = keccak256(reportData); 143 | bytes32 h = keccak256(abi.encodePacked(hashedReport, reportContext)); 144 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(i_donDigest, h); 145 | vm.stopPrank(); 146 | 147 | rawRs[0] = r; 148 | rawSs[0] = s; 149 | rawVs = bytes32(uint256(v)); 150 | 151 | return abi.encode(reportContext, reportData, rawRs, rawSs, rawVs); 152 | } 153 | 154 | function toUint32(uint256 timestamp) private pure returns (uint32) { 155 | if (timestamp > type(uint32).max) { 156 | revert MockReportGenerator__CastOverflow(); 157 | } 158 | return uint32(timestamp); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/data-streams/MockRewardManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol"; 5 | import {IERC20} from 6 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 7 | import {SafeERC20} from 8 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; 9 | 10 | // import {IRewardManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IRewardManager.sol"; 11 | import {IRewardManager} from "./interfaces/IRewardManager.sol"; 12 | 13 | contract MockRewardManager is IRewardManager { 14 | using SafeERC20 for IERC20; 15 | 16 | error Unauthorized(); 17 | 18 | address public immutable i_linkAddress; 19 | address public s_feeManagerAddress; 20 | 21 | event FeePaid(IRewardManager.FeePayment[] payments, address payer); 22 | 23 | modifier onlyFeeManager() { 24 | if (msg.sender != s_feeManagerAddress) revert Unauthorized(); 25 | _; 26 | } 27 | 28 | constructor(address linkAddress) { 29 | i_linkAddress = linkAddress; 30 | } 31 | 32 | function onFeePaid(FeePayment[] calldata payments, address payer) external override onlyFeeManager { 33 | uint256 totalFeeAmount; 34 | for (uint256 i; i < payments.length; ++i) { 35 | unchecked { 36 | // Tally the total payable fees 37 | totalFeeAmount += payments[i].amount; 38 | } 39 | } 40 | 41 | // Transfer fees to this contract 42 | IERC20(i_linkAddress).safeTransferFrom(payer, address(this), totalFeeAmount); 43 | 44 | emit FeePaid(payments, payer); 45 | } 46 | 47 | function claimRewards(bytes32[] memory /*poolIds*/ ) external pure override { 48 | revert("Not implemented"); 49 | } 50 | 51 | function setFeeManager(address newFeeManager) external override { 52 | s_feeManagerAddress = newFeeManager; 53 | } 54 | 55 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 56 | return interfaceId == this.onFeePaid.selector; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/data-streams/MockVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {IERC165} from 5 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; 6 | 7 | contract MockVerifier is IERC165 { 8 | error AccessForbidden(); 9 | error InactiveFeed(bytes32 feedId); 10 | error DigestInactive(bytes32 feedId, bytes32 configDigest); 11 | error BadVerification(); 12 | error InvalidV(); 13 | error AlreadyUsedSignature(); 14 | error InvalidSignatureLength(); 15 | error InvalidS(); 16 | 17 | // secp256k1 Curve Order N / 2 (to prevent signature malleability s <= N / 2) 18 | uint256 constant N_2 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0; 19 | // Generated from uint256(keccak256(abi.encodePacked("Mock Data Streams DON"))); 20 | address constant MOCK_DATA_STREAM_DON_ADDRESS = 0x143f40b47ab222503b787e98940023c59fc29984; 21 | 22 | address internal immutable i_verifierProxy; 23 | 24 | mapping(bytes32 => mapping(bytes32 => bool)) internal s_inactiveDigests; 25 | mapping(bytes32 => bool) internal s_inactiveFeeds; 26 | mapping(bytes => bool) internal s_verifiedSignatures; 27 | 28 | event ReportVerified(bytes32 indexed feedId, address indexed sender); 29 | 30 | constructor(address verifierProxy) { 31 | i_verifierProxy = verifierProxy; 32 | } 33 | 34 | function verify(bytes calldata signedReport, address sender) external returns (bytes memory verifierResponse) { 35 | if (msg.sender != i_verifierProxy) revert AccessForbidden(); 36 | 37 | ( 38 | bytes32[3] memory reportContext, 39 | bytes memory reportData, 40 | bytes32[] memory rs, 41 | bytes32[] memory ss, 42 | bytes32 rawVs 43 | ) = abi.decode(signedReport, (bytes32[3], bytes, bytes32[], bytes32[], bytes32)); 44 | 45 | // The feed ID is the first 32 bytes of the report data. 46 | bytes32 feedId = bytes32(reportData); 47 | if (s_inactiveFeeds[feedId]) revert InactiveFeed(feedId); 48 | 49 | bytes32 configDigest = reportContext[0]; 50 | if (s_inactiveDigests[feedId][configDigest]) revert DigestInactive(feedId, configDigest); 51 | 52 | // Decoding recid (v) like this works only for the mock verifier. In fork mode, use Verifier.sol from @chainlink/contracts. 53 | uint8 v = uint8(uint256(rawVs)); 54 | // Recid (v) can also be 0 or 1, however 0x01 precompile expects 27 or 28. 55 | if (v < 27) v += 27; 56 | // Prevents signature malleability 57 | if (v != 27 && v != 28) revert InvalidV(); 58 | 59 | bytes memory signature = abi.encodePacked(rs[0], ss[0], v); 60 | 61 | // Prevents replay attacks 62 | if (s_verifiedSignatures[signature]) revert AlreadyUsedSignature(); 63 | 64 | // MockReportGenerator will only generate standard "r,s,v" signatures. EIP-2098 and ERC-1271 are not supported. 65 | if (signature.length != 65) revert InvalidSignatureLength(); 66 | 67 | // Prevents signature malleability using EIP-2 68 | if (uint256(ss[0]) > N_2) revert InvalidS(); 69 | 70 | // Expired signatures are handled in MockVerifierProxy.sol 71 | // In local mode we don't care about cross-chain replay attacks, because everything is on one local network. 72 | 73 | bytes32 hashedReport = keccak256(reportData); 74 | bytes32 h = keccak256(abi.encodePacked(hashedReport, reportContext)); 75 | 76 | address signer = ecrecover(h, v, rs[0], ss[0]); 77 | if (signer != MOCK_DATA_STREAM_DON_ADDRESS) revert BadVerification(); 78 | 79 | s_verifiedSignatures[signature] = true; 80 | emit ReportVerified(feedId, sender); 81 | 82 | return reportData; 83 | } 84 | 85 | function deactivateConfig(bytes32 feedId, bytes32 configDigest) external { 86 | s_inactiveDigests[feedId][configDigest] = true; 87 | } 88 | 89 | function activateConfig(bytes32 feedId, bytes32 configDigest) external { 90 | s_inactiveDigests[feedId][configDigest] = false; 91 | } 92 | 93 | function deactivateFeed(bytes32 feedId) external { 94 | s_inactiveFeeds[feedId] = true; 95 | } 96 | 97 | function activateFeed(bytes32 feedId) external { 98 | s_inactiveFeeds[feedId] = false; 99 | } 100 | 101 | function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { 102 | return interfaceId == this.verify.selector; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/data-streams/MockVerifierProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol"; 5 | import {IERC165} from 6 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; 7 | 8 | // import {IVerifier} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IVerifier.sol"; 9 | // import {IVerifierFeeManager} from "@chainlink/contracts/src/v0.8/llo-feeds/interfaces/IVerifierFeeManager.sol"; 10 | import {IVerifier} from "./interfaces/IVerifier.sol"; 11 | import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; 12 | 13 | contract MockVerifierProxy is OwnerIsCreator { 14 | error ZeroAddress(); 15 | error VerifierInvalid(); 16 | error VerifierNotFound(); 17 | 18 | address internal s_verifier; 19 | IVerifierFeeManager public s_feeManager; 20 | 21 | event VerifierInitialized(address indexed verifierAddress); 22 | 23 | modifier onlyValidVerifier(address verifierAddress) { 24 | if (verifierAddress == address(0)) revert ZeroAddress(); 25 | if (!IERC165(verifierAddress).supportsInterface(IVerifier.verify.selector)) revert VerifierInvalid(); 26 | _; 27 | } 28 | 29 | function verify(bytes calldata payload, bytes calldata parameterPayload) external payable returns (bytes memory) { 30 | IVerifierFeeManager feeManager = s_feeManager; 31 | 32 | // Bill the verifier 33 | if (address(feeManager) != address(0)) { 34 | feeManager.processFee{value: msg.value}(payload, parameterPayload, msg.sender); 35 | } 36 | 37 | return _verify(payload); 38 | } 39 | 40 | function verifyBulk(bytes[] calldata payloads, bytes calldata parameterPayload) 41 | external 42 | payable 43 | returns (bytes[] memory verifiedReports) 44 | { 45 | IVerifierFeeManager feeManager = s_feeManager; 46 | 47 | // Bill the verifier 48 | if (address(feeManager) != address(0)) { 49 | feeManager.processFeeBulk{value: msg.value}(payloads, parameterPayload, msg.sender); 50 | } 51 | 52 | // Verify reports 53 | verifiedReports = new bytes[](payloads.length); 54 | for (uint256 i; i < payloads.length; ++i) { 55 | verifiedReports[i] = _verify(payloads[i]); 56 | } 57 | 58 | return verifiedReports; 59 | } 60 | 61 | function _verify(bytes calldata payload) internal returns (bytes memory) { 62 | if (s_verifier == address(0)) revert VerifierNotFound(); 63 | 64 | return IVerifier(s_verifier).verify(payload, msg.sender); 65 | } 66 | 67 | function initializeVerifier(address verifierAddress) external onlyOwner onlyValidVerifier(verifierAddress) { 68 | s_verifier = verifierAddress; 69 | emit VerifierInitialized(verifierAddress); 70 | } 71 | 72 | function getVerifier(bytes32 /*configDigest*/ ) external view returns (address) { 73 | return s_verifier; 74 | } 75 | 76 | function setFeeManager(IVerifierFeeManager feeManager) external { 77 | s_feeManager = feeManager; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/data-streams/Register.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title Register Contract 5 | /// @notice This contract allows storing and retrieving network details for various chains. 6 | /// @dev Stores network details in a mapping based on chain IDs. 7 | contract Register { 8 | struct NetworkDetails { 9 | address verifierProxyAddress; 10 | address linkAddress; 11 | } 12 | 13 | /// @notice Mapping to store network details based on chain ID. 14 | mapping(uint256 chainId => NetworkDetails) internal s_networkDetails; 15 | 16 | /// @notice Constructor to initialize the network details for various chains. 17 | constructor() { 18 | // Arbitrum Sepolia 19 | s_networkDetails[421614] = NetworkDetails({ 20 | verifierProxyAddress: 0x2ff010DEbC1297f19579B4246cad07bd24F2488A, 21 | linkAddress: 0xb1D4538B4571d411F07960EF2838Ce337FE1E80E 22 | }); 23 | 24 | // Avalanche Fuji 25 | s_networkDetails[43113] = NetworkDetails({ 26 | verifierProxyAddress: 0x2bf612C65f5a4d388E687948bb2CF842FFb8aBB3, 27 | linkAddress: 0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846 28 | }); 29 | 30 | // Base Sepolia 31 | s_networkDetails[84532] = NetworkDetails({ 32 | verifierProxyAddress: 0x8Ac491b7c118a0cdcF048e0f707247fD8C9575f9, 33 | linkAddress: 0xE4aB69C077896252FAFBD49EFD26B5D171A32410 34 | }); 35 | 36 | // opBNB Testnet 37 | s_networkDetails[5611] = 38 | NetworkDetails({verifierProxyAddress: 0x001225Aca0efe49Dbb48233aB83a9b4d177b581A, linkAddress: address(0)}); 39 | 40 | // Soneium Minato Testnet 41 | s_networkDetails[1946] = NetworkDetails({ 42 | verifierProxyAddress: 0x26603bAC5CE09DAE5604700B384658AcA13AD6ae, 43 | linkAddress: 0x7ea13478Ea3961A0e8b538cb05a9DF0477c79Cd2 44 | }); 45 | } 46 | 47 | /** 48 | * @notice Retrieves network details for a given chain ID. 49 | * 50 | * @param chainId - The ID of the chain to get the details for. 51 | * @return networkDetails - The network details for the specified chain ID. 52 | */ 53 | function getNetworkDetails(uint256 chainId) external view returns (NetworkDetails memory networkDetails) { 54 | networkDetails = s_networkDetails[chainId]; 55 | } 56 | 57 | /** 58 | * @notice Sets the network details for a given chain ID. 59 | * 60 | * @param chainId - The ID of the chain to set the details for. 61 | * @param networkDetails - The network details to set for the specified chain ID. 62 | */ 63 | function setNetworkDetails(uint256 chainId, NetworkDetails memory networkDetails) external { 64 | s_networkDetails[chainId] = networkDetails; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/data-streams/ReportVersions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | abstract contract ReportVersions { 5 | /** 6 | * @dev Represents a data report from a Data Streams stream for v2 schema (crypto streams). 7 | * The `price` value is carried to either 8 or 18 decimal places, depending on the stream. 8 | * For more information, see https://docs.chain.link/data-streams/crypto-streams and https://docs.chain.link/data-streams/reference/report-schema 9 | */ 10 | struct ReportV2 { 11 | bytes32 feedId; // The feed ID the report has data for 12 | uint32 validFromTimestamp; // Earliest timestamp for which price is applicable 13 | uint32 observationsTimestamp; // Latest timestamp for which price is applicable 14 | uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (WETH/ETH) 15 | uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK 16 | uint32 expiresAt; // Latest timestamp where the report can be verified on-chain 17 | int192 benchmarkPrice; // DON consensus median price, carried to 8 decimal places 18 | } 19 | 20 | /** 21 | * @dev Represents a data report from a Data Streams stream for v3 schema (crypto streams). 22 | * The `price`, `bid`, and `ask` values are carried to either 8 or 18 decimal places, depending on the stream. 23 | * For more information, see https://docs.chain.link/data-streams/crypto-streams and https://docs.chain.link/data-streams/reference/report-schema 24 | */ 25 | struct ReportV3 { 26 | bytes32 feedId; // The stream ID the report has data for. 27 | uint32 validFromTimestamp; // Earliest timestamp for which price is applicable. 28 | uint32 observationsTimestamp; // Latest timestamp for which price is applicable. 29 | uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH). 30 | uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK. 31 | uint32 expiresAt; // Latest timestamp where the report can be verified onchain. 32 | int192 price; // DON consensus median price (8 or 18 decimals). 33 | int192 bid; // Simulated price impact of a buy order up to the X% depth of liquidity utilisation (8 or 18 decimals). 34 | int192 ask; // Simulated price impact of a sell order up to the X% depth of liquidity utilisation (8 or 18 decimals). 35 | } 36 | 37 | /** 38 | * @dev Represents a data report from a Data Streams stream for v4 schema (RWA stream). 39 | * The `price` value is carried to either 8 or 18 decimal places, depending on the stream. 40 | * The `marketStatus` indicates whether the market is currently open. Possible values: `0` (`Unknown`), `1` (`Closed`), `2` (`Open`). 41 | * For more information, see https://docs.chain.link/data-streams/rwa-streams and https://docs.chain.link/data-streams/reference/report-schema-v4 42 | */ 43 | struct ReportV4 { 44 | bytes32 feedId; // The stream ID the report has data for. 45 | uint32 validFromTimestamp; // Earliest timestamp for which price is applicable. 46 | uint32 observationsTimestamp; // Latest timestamp for which price is applicable. 47 | uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH). 48 | uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK. 49 | uint32 expiresAt; // Latest timestamp where the report can be verified onchain. 50 | int192 price; // DON consensus median benchmark price (8 or 18 decimals). 51 | uint32 marketStatus; // The DON's consensus on whether the market is currently open. 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/data-streams/interfaces/IRewardManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {IERC165} from 5 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; 6 | 7 | interface IRewardManager is IERC165 { 8 | function onFeePaid(FeePayment[] calldata payments, address payee) external; 9 | function claimRewards(bytes32[] calldata poolIds) external; 10 | function setFeeManager(address newFeeManager) external; 11 | 12 | struct FeePayment { 13 | bytes32 poolId; 14 | uint192 amount; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/data-streams/interfaces/IVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IVerifier { 5 | function verify(bytes calldata signedReport, address sender) external returns (bytes memory verifierResponse); 6 | function activateConfig(bytes32 feedId, bytes32 configDigest) external; 7 | function deactivateConfig(bytes32 feedId, bytes32 configDigest) external; 8 | function activateFeed(bytes32 feedId) external; 9 | function deactivateFeed(bytes32 feedId) external; 10 | } 11 | -------------------------------------------------------------------------------- /src/data-streams/interfaces/IVerifierFeeManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {IERC165} from 5 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol"; 6 | 7 | interface IVerifierFeeManager is IERC165 { 8 | function processFee(bytes calldata payload, bytes calldata parameterPayload, address subscriber) external payable; 9 | function processFeeBulk(bytes[] calldata payloads, bytes calldata parameterPayload, address subscriber) 10 | external 11 | payable; 12 | } 13 | -------------------------------------------------------------------------------- /src/shared/LinkToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {ERC677} from "@chainlink/contracts-ccip/src/v0.8/shared/token/ERC677/ERC677.sol"; 5 | 6 | /// @title LinkToken 7 | /// @notice This contract implements the ChainLink Token (LINK) using the ERC677 standard. 8 | /// @dev Inherits from the ERC677 token contract and initializes with a fixed total supply and standard token details. 9 | contract LinkToken is ERC677 { 10 | /// @notice The total supply of LINK tokens. 11 | uint private constant TOTAL_SUPPLY = 10 ** 27; 12 | 13 | /// @notice The name of the LINK token. 14 | string private constant NAME = "ChainLink Token"; 15 | 16 | /// @notice The symbol of the LINK token. 17 | string private constant SYMBOL = "LINK"; 18 | 19 | /** 20 | * @notice Constructor to initialize the LinkToken contract with a fixed total supply, name, and symbol. 21 | * @dev Calls the ERC677 constructor with the name and symbol, and then mints the total supply to the contract deployer. 22 | */ 23 | constructor() ERC677(NAME, SYMBOL) { 24 | _onCreate(); 25 | } 26 | 27 | /** 28 | * @notice Hook that is called when this contract is created. 29 | * @dev Useful to override constructor behaviour in child contracts (e.g., LINK bridge tokens). 30 | * The default implementation mints 10**27 tokens to the contract deployer. 31 | */ 32 | function _onCreate() internal virtual { 33 | _mint(msg.sender, TOTAL_SUPPLY); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/shared/WETH9.sol: -------------------------------------------------------------------------------- 1 | // Submitted for verification at Etherscan.io on 2017-12-12 2 | 3 | // Copyright (C) 2015, 2016, 2017 Dapphub 4 | 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | pragma solidity ^0.8.19; 18 | 19 | contract WETH9 { 20 | string public name = "Wrapped Ether"; 21 | string public symbol = "WETH"; 22 | uint8 public decimals = 18; 23 | 24 | event Approval(address indexed src, address indexed guy, uint256 wad); 25 | event Transfer(address indexed src, address indexed dst, uint256 wad); 26 | event Deposit(address indexed dst, uint256 wad); 27 | event Withdrawal(address indexed src, uint256 wad); 28 | 29 | mapping(address => uint256) public balanceOf; 30 | mapping(address => mapping(address => uint256)) public allowance; 31 | 32 | receive() external payable { 33 | _deposit(); 34 | } 35 | 36 | function _deposit() internal { 37 | balanceOf[msg.sender] += msg.value; 38 | emit Deposit(msg.sender, msg.value); 39 | } 40 | 41 | function deposit() external payable { 42 | _deposit(); 43 | } 44 | 45 | function withdraw(uint256 wad) external { 46 | require(balanceOf[msg.sender] >= wad); 47 | balanceOf[msg.sender] -= wad; 48 | payable(msg.sender).transfer(wad); 49 | emit Withdrawal(msg.sender, wad); 50 | } 51 | 52 | function totalSupply() public view returns (uint256) { 53 | return address(this).balance; 54 | } 55 | 56 | function approve(address guy, uint256 wad) public returns (bool) { 57 | allowance[msg.sender][guy] = wad; 58 | emit Approval(msg.sender, guy, wad); 59 | return true; 60 | } 61 | 62 | function transfer(address dst, uint256 wad) public returns (bool) { 63 | return transferFrom(msg.sender, dst, wad); 64 | } 65 | 66 | function transferFrom( 67 | address src, 68 | address dst, 69 | uint256 wad 70 | ) public returns (bool) { 71 | require(balanceOf[src] >= wad); 72 | 73 | if ( 74 | src != msg.sender && allowance[src][msg.sender] != type(uint128).max 75 | ) { 76 | require(allowance[src][msg.sender] >= wad); 77 | allowance[src][msg.sender] -= wad; 78 | } 79 | 80 | balanceOf[src] -= wad; 81 | balanceOf[dst] += wad; 82 | 83 | emit Transfer(src, dst, wad); 84 | 85 | return true; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/ccip/BasicTokenSender.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.24; 3 | 4 | import {IERC20} from 5 | "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 6 | import {SafeERC20} from 7 | "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; 8 | import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; 9 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 10 | 11 | /** 12 | * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. 13 | * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. 14 | * DO NOT USE THIS CODE IN PRODUCTION. 15 | */ 16 | contract BasicTokenSender { 17 | using SafeERC20 for IERC20; 18 | 19 | address immutable i_router; 20 | 21 | event MessageSent(bytes32 messageId); 22 | 23 | constructor(address router) { 24 | i_router = router; 25 | } 26 | 27 | receive() external payable {} 28 | 29 | function send(uint64 destinationChainSelector, address receiver, Client.EVMTokenAmount[] memory tokensToSendDetails) 30 | external 31 | payable 32 | { 33 | uint256 length = tokensToSendDetails.length; 34 | 35 | for (uint256 i = 0; i < length;) { 36 | IERC20(tokensToSendDetails[i].token).safeTransferFrom( 37 | msg.sender, address(this), tokensToSendDetails[i].amount 38 | ); 39 | IERC20(tokensToSendDetails[i].token).approve(i_router, tokensToSendDetails[i].amount); 40 | 41 | unchecked { 42 | ++i; 43 | } 44 | } 45 | 46 | Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ 47 | receiver: abi.encode(receiver), 48 | data: "", 49 | tokenAmounts: tokensToSendDetails, 50 | extraArgs: "", 51 | feeToken: address(0) 52 | }); 53 | 54 | uint256 fee = IRouterClient(i_router).getFee(destinationChainSelector, message); 55 | 56 | bytes32 messageId = IRouterClient(i_router).ccipSend{value: fee}(destinationChainSelector, message); 57 | 58 | emit MessageSent(messageId); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/ccip/CCIPReceiver_Unsafe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.24; 3 | 4 | import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; 5 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 6 | 7 | contract CCIPReceiver_Unsafe is CCIPReceiver { 8 | string public text; 9 | 10 | constructor(address router) CCIPReceiver(router) {} 11 | 12 | function _ccipReceive(Client.Any2EVMMessage memory message) internal override { 13 | text = abi.decode(message.data, (string)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/ccip/CCIPSender_Unsafe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 5 | import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; 6 | import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 7 | 8 | contract CCIPSender_Unsafe { 9 | address link; 10 | address router; 11 | 12 | constructor(address _link, address _router) { 13 | link = _link; 14 | router = _router; 15 | } 16 | 17 | function send( 18 | address receiver, 19 | string memory someText, 20 | uint64 destinationChainSelector, 21 | address _token, 22 | uint256 _amount 23 | ) external returns (bytes32 messageId) { 24 | Client.EVMTokenAmount[] 25 | memory tokenAmounts = new Client.EVMTokenAmount[](1); 26 | Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({ 27 | token: _token, 28 | amount: _amount 29 | }); 30 | tokenAmounts[0] = tokenAmount; 31 | 32 | Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ 33 | receiver: abi.encode(receiver), 34 | data: abi.encode(someText), 35 | tokenAmounts: tokenAmounts, 36 | extraArgs: "", 37 | feeToken: link 38 | }); 39 | 40 | IERC20(_token).approve(address(router), _amount); 41 | 42 | uint256 fee = IRouterClient(router).getFee( 43 | destinationChainSelector, 44 | message 45 | ); 46 | IERC20(link).approve(address(router), fee); 47 | 48 | messageId = IRouterClient(router).ccipSend( 49 | destinationChainSelector, 50 | message 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/ccip/Ping.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 5 | import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; 6 | import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 7 | import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; 8 | 9 | contract Ping is CCIPReceiver { 10 | address link; 11 | address router; 12 | 13 | string public PONG; 14 | 15 | constructor(address _link, address _router) CCIPReceiver(_router) { 16 | link = _link; 17 | router = _router; 18 | } 19 | 20 | function send( 21 | address receiver, 22 | uint64 destinationChainSelector 23 | ) external returns (bytes32 messageId) { 24 | Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ 25 | receiver: abi.encode(receiver), 26 | data: abi.encode("Ping"), 27 | tokenAmounts: new Client.EVMTokenAmount[](0), 28 | extraArgs: Client._argsToBytes( 29 | // Additional arguments, setting gas limit 30 | Client.EVMExtraArgsV1({gasLimit: 500_000}) 31 | ), 32 | feeToken: link 33 | }); 34 | 35 | uint256 fee = IRouterClient(router).getFee( 36 | destinationChainSelector, 37 | message 38 | ); 39 | 40 | IERC20(link).approve(address(router), fee); 41 | 42 | messageId = IRouterClient(router).ccipSend( 43 | destinationChainSelector, 44 | message 45 | ); 46 | } 47 | 48 | function _ccipReceive( 49 | Client.Any2EVMMessage memory message 50 | ) internal override { 51 | PONG = abi.decode(message.data, (string)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/ccip/Pong.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 5 | import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; 6 | import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 7 | import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; 8 | 9 | contract Pong is CCIPReceiver { 10 | address link; 11 | address router; 12 | 13 | string public PING; 14 | 15 | constructor(address _link, address _router) CCIPReceiver(_router) { 16 | link = _link; 17 | router = _router; 18 | } 19 | 20 | function _ccipReceive( 21 | Client.Any2EVMMessage memory message 22 | ) internal override { 23 | PING = abi.decode(message.data, (string)); 24 | 25 | Client.EVM2AnyMessage memory replyMessage = Client.EVM2AnyMessage({ 26 | receiver: message.sender, 27 | data: abi.encode("Pong"), 28 | tokenAmounts: new Client.EVMTokenAmount[](0), 29 | extraArgs: "", 30 | feeToken: link 31 | }); 32 | 33 | uint256 fee = IRouterClient(router).getFee( 34 | message.sourceChainSelector, 35 | replyMessage 36 | ); 37 | IERC20(link).approve(address(router), fee); 38 | 39 | IRouterClient(router).ccipSend( 40 | message.sourceChainSelector, 41 | replyMessage 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/data-feeds/BasicDataConsumerV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.7; 3 | 4 | import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; 5 | 6 | /** 7 | * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED 8 | * VALUES FOR CLARITY. 9 | * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. 10 | * DO NOT USE THIS CODE IN PRODUCTION. 11 | */ 12 | 13 | /** 14 | * If you are reading data feeds on L2 networks, you must 15 | * check the latest answer from the L2 Sequencer Uptime 16 | * Feed to ensure that the data is accurate in the event 17 | * of an L2 sequencer outage. See the 18 | * https://docs.chain.link/data-feeds/l2-sequencer-feeds 19 | * page for details. 20 | */ 21 | contract BasicDataConsumerV3 { 22 | AggregatorV3Interface internal dataFeed; 23 | 24 | constructor(address dataFeedAddress) { 25 | dataFeed = AggregatorV3Interface(dataFeedAddress); 26 | } 27 | 28 | function getChainlinkDataFeedLatestAnswer() public view returns (int256) { 29 | ( 30 | , 31 | /* uint80 roundID */ 32 | int256 answer /*uint startedAt*/ /*uint timeStamp*/ /*uint80 answeredInRound*/, 33 | , 34 | , 35 | 36 | ) = dataFeed.latestRoundData(); 37 | return answer; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/data-streams/ChainlinkDataStreamProvider.sol: -------------------------------------------------------------------------------- 1 | // Simplifed version of https://github.com/gmx-io/gmx-synthetics/blob/main/contracts/oracle/ChainlinkDataStreamProvider.sol used for testing purposes 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.0; 4 | 5 | interface IChainlinkDataStreamVerifier { 6 | /** 7 | * @notice Verifies that the data encoded has been signed 8 | * correctly by routing to the correct verifier, and bills the user if applicable. 9 | * @param payload The encoded data to be verified, including the signed 10 | * report. 11 | * @param parameterPayload fee metadata for billing. For the current implementation this is just the abi-encoded fee token ERC-20 address 12 | * @return verifierResponse The encoded report from the verifier. 13 | */ 14 | function verify(bytes calldata payload, bytes calldata parameterPayload) 15 | external 16 | payable 17 | returns (bytes memory verifierResponse); 18 | } 19 | 20 | contract ChainlinkDataStreamProvider { 21 | bytes32 public constant DATA_STREAM_ID = keccak256(abi.encode("DATA_STREAM_ID")); 22 | bytes32 public constant CHAINLINK_PAYMENT_TOKEN = keccak256(abi.encode("CHAINLINK_PAYMENT_TOKEN")); 23 | 24 | IChainlinkDataStreamVerifier public immutable verifier; 25 | 26 | mapping(bytes32 => address) public addressValues; 27 | mapping(bytes32 => bytes32) public bytes32Values; 28 | 29 | // bid: min price, highest buy price 30 | // ask: max price, lowest sell price 31 | struct Report { 32 | bytes32 feedId; // The feed ID the report has data for 33 | uint32 validFromTimestamp; // Earliest timestamp for which price is applicable 34 | uint32 observationsTimestamp; // Latest timestamp for which price is applicable 35 | uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (WETH/ETH) 36 | uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK 37 | uint32 expiresAt; // Latest timestamp where the report can be verified onchain 38 | int192 price; // DON consensus median price, carried to 8 decimal places 39 | int192 bid; // Simulated price impact of a buy order up to the X% depth of liquidity utilisation 40 | int192 ask; // Simulated price impact of a sell order up to the X% depth of liquidity utilisation 41 | } 42 | 43 | error EmptyChainlinkPaymentToken(); 44 | error EmptyDataStreamFeedId(address token); 45 | error InvalidDataStreamFeedId(address token, bytes32 reportFeedId, bytes32 expectedFeedId); 46 | error InvalidDataStreamPrices(address token, int192 bid, int192 ask); 47 | error InvalidDataStreamBidAsk(address token, int192 bid, int192 ask); 48 | 49 | constructor(address _verifier, address linkTokenAddress) { 50 | verifier = IChainlinkDataStreamVerifier(_verifier); 51 | addressValues[CHAINLINK_PAYMENT_TOKEN] = linkTokenAddress; 52 | } 53 | 54 | function setBytes32(address token, bytes32 feedId) external { 55 | bytes32Values[dataStreamIdKey(token)] = feedId; 56 | } 57 | 58 | // @dev key for data stream feed ID 59 | // @param token the token to get the key for 60 | // @return key for data stream feed ID 61 | function dataStreamIdKey(address token) internal pure returns (bytes32) { 62 | return keccak256(abi.encode(DATA_STREAM_ID, token)); 63 | } 64 | 65 | function getOraclePrice(address token, bytes memory data) 66 | external 67 | returns (address token_, int192 bid, int192 ask, uint32 timestamp) 68 | { 69 | bytes32 feedId = bytes32Values[dataStreamIdKey(token)]; 70 | if (feedId == bytes32(0)) { 71 | revert EmptyDataStreamFeedId(token); 72 | } 73 | 74 | bytes memory payloadParameter = _getPayloadParameter(); 75 | bytes memory verifierResponse = verifier.verify(data, payloadParameter); 76 | 77 | Report memory report = abi.decode(verifierResponse, (Report)); 78 | 79 | if (feedId != report.feedId) { 80 | revert InvalidDataStreamFeedId(token, report.feedId, feedId); 81 | } 82 | 83 | if (report.bid <= 0 || report.ask <= 0) { 84 | revert InvalidDataStreamPrices(token, report.bid, report.ask); 85 | } 86 | 87 | if (report.bid > report.ask) { 88 | revert InvalidDataStreamBidAsk(token, report.bid, report.ask); 89 | } 90 | 91 | return (token, report.bid, report.ask, report.observationsTimestamp); 92 | } 93 | 94 | function _getPayloadParameter() internal view returns (bytes memory) { 95 | // LINK token address 96 | address feeToken = addressValues[CHAINLINK_PAYMENT_TOKEN]; 97 | 98 | if (feeToken == address(0)) { 99 | revert EmptyChainlinkPaymentToken(); 100 | } 101 | 102 | return abi.encode(feeToken); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/data-streams/ClientReportsVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {IERC20} from 5 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; 6 | import {SafeERC20} from 7 | "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; 8 | 9 | library Common { 10 | struct Asset { 11 | address token; 12 | uint256 amount; 13 | } 14 | } 15 | 16 | interface IVerifierProxy { 17 | function verify(bytes calldata payload, bytes calldata parameterPayload) 18 | external 19 | payable 20 | returns (bytes memory verifierResponse); 21 | 22 | function verifyBulk(bytes[] calldata payloads, bytes calldata parameterPayload) 23 | external 24 | payable 25 | returns (bytes[] memory verifiedReports); 26 | 27 | function s_feeManager() external view returns (address); 28 | } 29 | 30 | interface IFeeManager { 31 | function getFeeAndReward(address subscriber, bytes memory unverifiedReport, address quoteAddress) 32 | external 33 | returns (Common.Asset memory, Common.Asset memory, uint256); 34 | 35 | function i_linkAddress() external view returns (address); 36 | 37 | function i_nativeAddress() external view returns (address); 38 | 39 | function i_rewardManager() external view returns (address); 40 | } 41 | 42 | /** 43 | * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE FOR DEMONSTRATION PURPOSES. 44 | * DO NOT USE THIS CODE IN PRODUCTION. 45 | */ 46 | contract ClientReportsVerifier { 47 | using SafeERC20 for IERC20; 48 | 49 | error NothingToWithdraw(); 50 | error NotOwner(address caller); 51 | error InvalidReportVersion(uint16 version); 52 | 53 | struct ReportV3 { 54 | bytes32 feedId; // The stream ID the report has data for. 55 | uint32 validFromTimestamp; // Earliest timestamp for which price is applicable. 56 | uint32 observationsTimestamp; // Latest timestamp for which price is applicable. 57 | uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH). 58 | uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK. 59 | uint32 expiresAt; // Latest timestamp where the report can be verified onchain. 60 | int192 price; // DON consensus median price (8 or 18 decimals). 61 | int192 bid; // Simulated price impact of a buy order up to the X% depth of liquidity utilisation (8 or 18 decimals). 62 | int192 ask; // Simulated price impact of a sell order up to the X% depth of liquidity utilisation (8 or 18 decimals). 63 | } 64 | 65 | struct ReportV4 { 66 | bytes32 feedId; // The stream ID the report has data for. 67 | uint32 validFromTimestamp; // Earliest timestamp for which price is applicable. 68 | uint32 observationsTimestamp; // Latest timestamp for which price is applicable. 69 | uint192 nativeFee; // Base cost to validate a transaction using the report, denominated in the chain’s native token (e.g., WETH/ETH). 70 | uint192 linkFee; // Base cost to validate a transaction using the report, denominated in LINK. 71 | uint32 expiresAt; // Latest timestamp where the report can be verified onchain. 72 | int192 price; // DON consensus median benchmark price (8 or 18 decimals). 73 | uint32 marketStatus; // The DON's consensus on whether the market is currently open. 74 | } 75 | 76 | IVerifierProxy public s_verifierProxy; 77 | 78 | address private s_owner; 79 | int192 public lastDecodedPrice; 80 | 81 | event DecodedPrice(int192 price); 82 | 83 | constructor(address _verifierProxy) { 84 | s_owner = msg.sender; 85 | s_verifierProxy = IVerifierProxy(_verifierProxy); 86 | } 87 | 88 | modifier onlyOwner() { 89 | if (msg.sender != s_owner) revert NotOwner(msg.sender); 90 | _; 91 | } 92 | 93 | function verifyReport(bytes memory unverifiedReport) external { 94 | IFeeManager feeManager = IFeeManager(s_verifierProxy.s_feeManager()); 95 | 96 | address rewardManager = feeManager.i_rewardManager(); 97 | 98 | // Decode unverified report to extract report data 99 | (, bytes memory reportData) = abi.decode(unverifiedReport, (bytes32[3], bytes)); 100 | 101 | // Extract report version from reportData 102 | uint16 reportVersion = (uint16(uint8(reportData[0])) << 8) | uint16(uint8(reportData[1])); 103 | 104 | // Validate report version 105 | if (reportVersion != 3 && reportVersion != 4) { 106 | revert InvalidReportVersion(uint8(reportVersion)); 107 | } 108 | 109 | // Set the fee token address (LINK in this case) 110 | address feeTokenAddress = feeManager.i_linkAddress(); 111 | 112 | // Calculate the fee required for report verification 113 | (Common.Asset memory fee,,) = feeManager.getFeeAndReward(address(this), reportData, feeTokenAddress); 114 | 115 | // Approve rewardManager to spend this contract's balance in fees 116 | IERC20(feeTokenAddress).approve(rewardManager, fee.amount); 117 | 118 | // Verify the report through the VerifierProxy 119 | bytes memory verifiedReportData = s_verifierProxy.verify(unverifiedReport, abi.encode(feeTokenAddress)); 120 | 121 | // Decode verified report data into the appropriate Report struct based on reportVersion 122 | if (reportVersion == 3) { 123 | // v3 report schema 124 | ReportV3 memory verifiedReport = abi.decode(verifiedReportData, (ReportV3)); 125 | 126 | // Log price from the verified report 127 | emit DecodedPrice(verifiedReport.price); 128 | 129 | // Store the price from the report 130 | lastDecodedPrice = verifiedReport.price; 131 | } else if (reportVersion == 4) { 132 | // v4 report schema 133 | ReportV4 memory verifiedReport = abi.decode(verifiedReportData, (ReportV4)); 134 | 135 | // Log price from the verified report 136 | emit DecodedPrice(verifiedReport.price); 137 | 138 | // Store the price from the report 139 | lastDecodedPrice = verifiedReport.price; 140 | } 141 | } 142 | 143 | function withdrawToken(address _beneficiary, address _token) external onlyOwner { 144 | uint256 amount = IERC20(_token).balanceOf(address(this)); 145 | 146 | if (amount == 0) revert NothingToWithdraw(); 147 | 148 | IERC20(_token).safeTransfer(_beneficiary, amount); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /test/e2e/ccip/PingPongFork.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, Vm} from "forge-std/Test.sol"; 5 | import {Ping} from "../../../src/test/ccip/Ping.sol"; 6 | import {Pong} from "../../../src/test/ccip/Pong.sol"; 7 | import {CCIPLocalSimulatorFork, Register} from "../../../src/ccip/CCIPLocalSimulatorFork.sol"; 8 | 9 | contract PingPongFork is Test { 10 | CCIPLocalSimulatorFork public ccipLocalSimulatorFork; 11 | Ping public ping; 12 | Pong public pong; 13 | 14 | Register.NetworkDetails sepoliaNetworkDetails; 15 | Register.NetworkDetails arbSepoliaNetworkDetails; 16 | 17 | uint256 sepoliaFork; 18 | uint256 arbSepoliaFork; 19 | 20 | function setUp() public { 21 | string memory ETHEREUM_SEPOLIA_RPC_URL = vm.envString("ETHEREUM_SEPOLIA_RPC_URL"); 22 | string memory ARBITRUM_SEPOLIA_RPC_URL = vm.envString("ARBITRUM_SEPOLIA_RPC_URL"); 23 | sepoliaFork = vm.createSelectFork(ETHEREUM_SEPOLIA_RPC_URL); 24 | arbSepoliaFork = vm.createFork(ARBITRUM_SEPOLIA_RPC_URL); 25 | 26 | ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); 27 | vm.makePersistent(address(ccipLocalSimulatorFork)); 28 | sepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); 29 | 30 | ping = new Ping(sepoliaNetworkDetails.linkAddress, sepoliaNetworkDetails.routerAddress); 31 | 32 | ccipLocalSimulatorFork.requestLinkFromFaucet(address(ping), 1 ether); 33 | 34 | vm.selectFork(arbSepoliaFork); 35 | arbSepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); 36 | pong = new Pong(arbSepoliaNetworkDetails.linkAddress, arbSepoliaNetworkDetails.routerAddress); 37 | 38 | ccipLocalSimulatorFork.requestLinkFromFaucet(address(pong), 1 ether); 39 | } 40 | 41 | function test_ForkPingPong() public { 42 | vm.selectFork(sepoliaFork); 43 | 44 | ping.send(address(pong), arbSepoliaNetworkDetails.chainSelector); 45 | ccipLocalSimulatorFork.switchChainAndRouteMessage(arbSepoliaFork); 46 | ccipLocalSimulatorFork.switchChainAndRouteMessage(sepoliaFork); 47 | 48 | assertEq(ping.PONG(), "Pong"); 49 | vm.selectFork(arbSepoliaFork); 50 | assertEq(pong.PING(), "Ping"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/e2e/ccip/TokenTransferorFork.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, Vm} from "forge-std/Test.sol"; 5 | import {TokenTransferor} from "../../../src/test/ccip/TokenTransferor.sol"; 6 | import {BurnMintERC677Helper, IERC20} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 7 | import {CCIPLocalSimulatorFork, Register} from "../../../src/ccip/CCIPLocalSimulatorFork.sol"; 8 | 9 | contract TokenTransferorFork is Test { 10 | CCIPLocalSimulatorFork public ccipLocalSimulatorFork; 11 | TokenTransferor public sender; 12 | BurnMintERC677Helper public ccipBnM; 13 | IERC20 public linkToken; 14 | address alice; 15 | 16 | uint256 sepoliaFork; 17 | uint256 arbSepoliaFork; 18 | 19 | function setUp() public { 20 | string memory ETHEREUM_SEPOLIA_RPC_URL = vm.envString("ETHEREUM_SEPOLIA_RPC_URL"); 21 | string memory ARBITRUM_SEPOLIA_RPC_URL = vm.envString("ARBITRUM_SEPOLIA_RPC_URL"); 22 | sepoliaFork = vm.createSelectFork(ETHEREUM_SEPOLIA_RPC_URL); 23 | arbSepoliaFork = vm.createFork(ARBITRUM_SEPOLIA_RPC_URL); 24 | 25 | ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); 26 | vm.makePersistent(address(ccipLocalSimulatorFork)); 27 | 28 | Register.NetworkDetails memory sepoliaNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); 29 | 30 | sender = new TokenTransferor(sepoliaNetworkDetails.routerAddress, sepoliaNetworkDetails.linkAddress); 31 | 32 | ccipBnM = BurnMintERC677Helper(sepoliaNetworkDetails.ccipBnMAddress); 33 | 34 | linkToken = IERC20(sepoliaNetworkDetails.linkAddress); 35 | 36 | alice = makeAddr("alice"); 37 | 38 | ccipLocalSimulatorFork.requestLinkFromFaucet(address(sender), 25 ether); 39 | } 40 | 41 | function test_forkTokenTransfer() external { 42 | uint256 amountToSend = 100; 43 | ccipBnM.drip(address(sender)); 44 | 45 | uint64 arbSepoliaChainSelector = 3478487238524512106; 46 | sender.allowlistDestinationChain(arbSepoliaChainSelector, true); 47 | 48 | uint256 balanceBefore = ccipBnM.balanceOf(address(sender)); 49 | 50 | sender.transferTokensPayLINK(arbSepoliaChainSelector, alice, address(ccipBnM), amountToSend); 51 | 52 | uint256 balanceAfer = ccipBnM.balanceOf(address(sender)); 53 | assertEq(balanceAfer, balanceBefore - amountToSend); 54 | 55 | ccipLocalSimulatorFork.switchChainAndRouteMessage(arbSepoliaFork); 56 | 57 | Register.NetworkDetails memory arbSepoliaNetworkDetails = 58 | ccipLocalSimulatorFork.getNetworkDetails(block.chainid); 59 | BurnMintERC677Helper ccipBnMArbSepolia = BurnMintERC677Helper(arbSepoliaNetworkDetails.ccipBnMAddress); 60 | 61 | assertEq(ccipBnMArbSepolia.balanceOf(alice), amountToSend); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/e2e/data-feeds/BasicDataConsumerV3.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | 6 | import {BasicDataConsumerV3} from "../../../src/test/data-feeds/BasicDataConsumerV3.sol"; 7 | 8 | contract BasicDataConsumerV3Test is Test { 9 | BasicDataConsumerV3 public consumer; 10 | address constant ETH_USD_AGGREGATOR_ADDRESS = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; 11 | uint256 constant BLOCK_NUMBER = 20163485; 12 | int256 constant EXPECTED_ANSWER_AT_PINNED_BLOCK_NUMBER = 329182212045; // retrieved from Etherscan 13 | 14 | uint256 ethereumMainnetForkId; 15 | 16 | function setUp() public { 17 | string memory ETHEREUM_MAINNET_RPC_URL = vm.envString("ETHEREUM_MAINNET_RPC_URL"); 18 | ethereumMainnetForkId = vm.createSelectFork(ETHEREUM_MAINNET_RPC_URL, BLOCK_NUMBER); 19 | 20 | consumer = new BasicDataConsumerV3(ETH_USD_AGGREGATOR_ADDRESS); 21 | } 22 | 23 | function test_forkSmoke() public { 24 | assertEq(vm.activeFork(), ethereumMainnetForkId); 25 | 26 | int256 answer = consumer.getChainlinkDataFeedLatestAnswer(); 27 | assertEq(answer, EXPECTED_ANSWER_AT_PINNED_BLOCK_NUMBER); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/smoke/ccip/PayWithNative.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {BasicTokenSender} from "../../../src/test/ccip/BasicTokenSender.sol"; 6 | import { 7 | CCIPLocalSimulator, IRouterClient, BurnMintERC677Helper 8 | } from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 9 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 10 | 11 | contract PayWithNativeTest is Test { 12 | CCIPLocalSimulator public ccipLocalSimulator; 13 | BasicTokenSender public sender; 14 | uint64 chainSelector; 15 | BurnMintERC677Helper ccipBnM; 16 | IRouterClient sourceRouter; 17 | address alice; 18 | address bob; 19 | 20 | function setUp() public { 21 | ccipLocalSimulator = new CCIPLocalSimulator(); 22 | (uint64 chainSelector_, IRouterClient sourceRouter_,,,, BurnMintERC677Helper ccipBnM_,) = 23 | ccipLocalSimulator.configuration(); 24 | 25 | sender = new BasicTokenSender(address(sourceRouter_)); 26 | 27 | chainSelector = chainSelector_; 28 | ccipBnM = ccipBnM_; 29 | sourceRouter = sourceRouter_; 30 | 31 | alice = makeAddr("alice"); 32 | bob = makeAddr("bob"); 33 | } 34 | 35 | function test_shouldPayInNativeCoin() external { 36 | vm.startPrank(alice); 37 | 38 | ccipBnM.drip(alice); 39 | uint256 amountToSend = 100; 40 | 41 | Client.EVMTokenAmount[] memory tokensToSendDetails = new Client.EVMTokenAmount[](1); 42 | Client.EVMTokenAmount memory tokenToSend = 43 | Client.EVMTokenAmount({token: address(ccipBnM), amount: amountToSend}); 44 | tokensToSendDetails[0] = tokenToSend; 45 | 46 | Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ 47 | receiver: abi.encode(alice), 48 | data: "", 49 | tokenAmounts: tokensToSendDetails, 50 | extraArgs: "", 51 | feeToken: address(0) 52 | }); 53 | 54 | uint256 fee = sourceRouter.getFee(chainSelector, message); 55 | 56 | uint256 balanceOfAliceBefore = ccipBnM.balanceOf(alice); 57 | uint256 balanceOfBobBefore = ccipBnM.balanceOf(bob); 58 | 59 | ccipBnM.increaseApproval(address(sender), amountToSend); 60 | assertEq(ccipBnM.allowance(alice, address(sender)), amountToSend); 61 | 62 | sender.send{value: fee}(chainSelector, bob, tokensToSendDetails); 63 | 64 | uint256 balanceOfAliceAfter = ccipBnM.balanceOf(alice); 65 | uint256 balanceOfBobAfter = ccipBnM.balanceOf(bob); 66 | assertEq(balanceOfAliceAfter, balanceOfAliceBefore - amountToSend); 67 | assertEq(balanceOfBobAfter, balanceOfBobBefore + amountToSend); 68 | 69 | vm.stopPrank(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/smoke/ccip/PingPong.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {Ping} from "../../../src/test/ccip/Ping.sol"; 6 | import {Pong} from "../../../src/test/ccip/Pong.sol"; 7 | import {CCIPLocalSimulator, IRouterClient, LinkToken} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 8 | 9 | contract PingPongTest is Test { 10 | CCIPLocalSimulator public ccipLocalSimulator; 11 | 12 | Ping public ping; 13 | Pong public pong; 14 | 15 | uint64 chainSelector; 16 | 17 | function setUp() public { 18 | ccipLocalSimulator = new CCIPLocalSimulator(); 19 | ( 20 | uint64 chainSelector_, 21 | IRouterClient sourceRouter, 22 | IRouterClient destinationRouter, 23 | , 24 | LinkToken linkToken, 25 | , 26 | 27 | ) = ccipLocalSimulator.configuration(); 28 | 29 | ping = new Ping(address(linkToken), address(sourceRouter)); 30 | pong = new Pong(address(linkToken), address(destinationRouter)); 31 | 32 | chainSelector = chainSelector_; 33 | } 34 | 35 | function test_pingPong() external { 36 | uint256 amountForFees = 1 ether; 37 | ccipLocalSimulator.requestLinkFromFaucet(address(ping), amountForFees); 38 | ccipLocalSimulator.requestLinkFromFaucet(address(pong), amountForFees); 39 | 40 | ping.send(address(pong), chainSelector); 41 | 42 | console2.log(pong.PING()); 43 | console2.log(ping.PONG()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/smoke/ccip/ProgrammableDefensiveTokenTransfers.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {ProgrammableDefensiveTokenTransfers} from "../../../src/test/ccip/ProgrammableDefensiveTokenTransfers.sol"; 6 | import {CCIPLocalSimulator, IRouterClient, LinkToken, BurnMintERC677Helper} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 7 | 8 | contract ProgrammableDefensiveTokenTransfersTest is Test { 9 | ProgrammableDefensiveTokenTransfers public sender; 10 | ProgrammableDefensiveTokenTransfers public receiver; 11 | CCIPLocalSimulator public ccipLocalSimulator; 12 | 13 | uint64 chainSelector; 14 | BurnMintERC677Helper ccipBnM; 15 | 16 | function setUp() public { 17 | ccipLocalSimulator = new CCIPLocalSimulator(); 18 | ( 19 | uint64 chainSelector_, 20 | IRouterClient sourceRouter, 21 | IRouterClient destinationRouter, 22 | , 23 | LinkToken linkToken, 24 | BurnMintERC677Helper ccipBnM_, 25 | 26 | ) = ccipLocalSimulator.configuration(); 27 | 28 | sender = new ProgrammableDefensiveTokenTransfers( 29 | address(sourceRouter), 30 | address(linkToken) 31 | ); 32 | 33 | receiver = new ProgrammableDefensiveTokenTransfers( 34 | address(destinationRouter), 35 | address(linkToken) 36 | ); 37 | 38 | chainSelector = chainSelector_; 39 | ccipBnM = ccipBnM_; 40 | } 41 | 42 | function prepareScenario() 43 | private 44 | returns (uint256 amountToSend, string memory textToSend) 45 | { 46 | amountToSend = 0.001 ether; 47 | uint256 amountForFees = 1 ether; 48 | textToSend = "Hello World"; 49 | 50 | ccipBnM.drip(address(sender)); 51 | 52 | ccipLocalSimulator.requestLinkFromFaucet( 53 | address(sender), 54 | amountForFees 55 | ); 56 | 57 | sender.allowlistDestinationChain(chainSelector, true); 58 | 59 | receiver.allowlistSourceChain(chainSelector, true); 60 | receiver.allowlistSender(address(sender), true); 61 | } 62 | 63 | function test_regularTransfer() public { 64 | (uint256 amountToSend, string memory textToSend) = prepareScenario(); 65 | 66 | bytes32 messageId = sender.sendMessagePayLINK( 67 | chainSelector, 68 | address(receiver), 69 | textToSend, 70 | address(ccipBnM), 71 | amountToSend 72 | ); 73 | 74 | ( 75 | bytes32 _messageId, 76 | string memory _text, 77 | address _tokenAddress, 78 | uint256 _tokenAmount 79 | ) = receiver.getLastReceivedMessageDetails(); 80 | 81 | assertEq(_messageId, messageId); 82 | assertEq(_text, textToSend); 83 | assertEq(_tokenAddress, address(ccipBnM)); 84 | assertEq(_tokenAmount, amountToSend); 85 | } 86 | 87 | function test_tokenRecovery() public { 88 | (uint256 amountToSend, string memory textToSend) = prepareScenario(); 89 | 90 | receiver.setSimRevert(true); 91 | 92 | uint256 senderBalanceBefore = ccipBnM.balanceOf(address(sender)); 93 | uint256 receiverBalanceBefore = ccipBnM.balanceOf(address(receiver)); 94 | 95 | bytes32 messageId = sender.sendMessagePayLINK( 96 | chainSelector, 97 | address(receiver), 98 | textToSend, 99 | address(ccipBnM), 100 | amountToSend 101 | ); 102 | 103 | ProgrammableDefensiveTokenTransfers.FailedMessage[] 104 | memory failedMessages = receiver.getFailedMessages(0, 1); 105 | 106 | assertEq(failedMessages[0].messageId, messageId); 107 | assertEq( 108 | uint8(failedMessages[0].errorCode), 109 | uint8(ProgrammableDefensiveTokenTransfers.ErrorCode.FAILED) 110 | ); 111 | 112 | receiver.retryFailedMessage(messageId, msg.sender); 113 | 114 | ProgrammableDefensiveTokenTransfers.FailedMessage[] 115 | memory failedMessagesAfter = receiver.getFailedMessages(0, 1); 116 | assertEq( 117 | uint8(failedMessagesAfter[0].errorCode), 118 | uint8(ProgrammableDefensiveTokenTransfers.ErrorCode.RESOLVED) 119 | ); 120 | 121 | uint256 senderBalanceAfter = ccipBnM.balanceOf(address(sender)); 122 | uint256 receiverBalanceAfter = ccipBnM.balanceOf(address(receiver)); 123 | 124 | assertEq(senderBalanceAfter, senderBalanceBefore - amountToSend); 125 | assertEq(receiverBalanceAfter, receiverBalanceBefore); 126 | assertEq( 127 | ccipBnM.balanceOf(msg.sender), 128 | receiverBalanceBefore + amountToSend 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/smoke/ccip/ProgrammableTokenTransfers.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {ProgrammableTokenTransfers} from "../../../src/test/ccip/ProgrammableTokenTransfers.sol"; 6 | import {CCIPLocalSimulator, IRouterClient, LinkToken, BurnMintERC677Helper} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 7 | 8 | contract ProgrammableTokenTransfersTest is Test { 9 | ProgrammableTokenTransfers public sender; 10 | ProgrammableTokenTransfers public receiver; 11 | CCIPLocalSimulator public ccipLocalSimulator; 12 | 13 | uint64 chainSelector; 14 | BurnMintERC677Helper ccipBnM; 15 | 16 | function setUp() public { 17 | ccipLocalSimulator = new CCIPLocalSimulator(); 18 | ( 19 | uint64 chainSelector_, 20 | IRouterClient sourceRouter, 21 | IRouterClient destinationRouter, 22 | , 23 | LinkToken linkToken, 24 | BurnMintERC677Helper ccipBnM_, 25 | 26 | ) = ccipLocalSimulator.configuration(); 27 | 28 | sender = new ProgrammableTokenTransfers( 29 | address(sourceRouter), 30 | address(linkToken) 31 | ); 32 | 33 | receiver = new ProgrammableTokenTransfers( 34 | address(destinationRouter), 35 | address(linkToken) 36 | ); 37 | 38 | chainSelector = chainSelector_; 39 | ccipBnM = ccipBnM_; 40 | } 41 | 42 | function testProgrammableTokenTransfer() public { 43 | uint256 amountToSend = 0.001 ether; 44 | uint256 amountForFees = 1 ether; 45 | string memory textToSend = "Hello World"; 46 | 47 | ccipBnM.drip(address(sender)); 48 | 49 | ccipLocalSimulator.requestLinkFromFaucet( 50 | address(sender), 51 | amountForFees 52 | ); 53 | 54 | sender.allowlistDestinationChain(chainSelector, true); 55 | 56 | receiver.allowlistSourceChain(chainSelector, true); 57 | receiver.allowlistSender(address(sender), true); 58 | 59 | uint256 senderBalanceBefore = ccipBnM.balanceOf(address(sender)); 60 | uint256 receiverBalanceBefore = ccipBnM.balanceOf(address(receiver)); 61 | 62 | bytes32 messageId = sender.sendMessagePayLINK( 63 | chainSelector, 64 | address(receiver), 65 | textToSend, 66 | address(ccipBnM), 67 | amountToSend 68 | ); 69 | 70 | ( 71 | bytes32 _messageId, 72 | string memory _text, 73 | address _tokenAddress, 74 | uint256 _tokenAmount 75 | ) = receiver.getLastReceivedMessageDetails(); 76 | 77 | assertEq(_messageId, messageId); 78 | assertEq(_text, textToSend); 79 | assertEq(_tokenAddress, address(ccipBnM)); 80 | assertEq(_tokenAmount, amountToSend); 81 | 82 | uint256 senderBalanceAfter = ccipBnM.balanceOf(address(sender)); 83 | uint256 receiverBalanceAfter = ccipBnM.balanceOf(address(receiver)); 84 | 85 | assertEq(senderBalanceAfter, senderBalanceBefore - amountToSend); 86 | assertEq(receiverBalanceAfter, receiverBalanceBefore + amountToSend); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/smoke/ccip/TokenTransferor.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {TokenTransferor} from "../../../src/test/ccip/TokenTransferor.sol"; 6 | import { 7 | CCIPLocalSimulator, 8 | IRouterClient, 9 | LinkToken, 10 | BurnMintERC677Helper 11 | } from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 12 | 13 | contract TokenTransferorTest is Test { 14 | TokenTransferor public tokenTransferor; 15 | CCIPLocalSimulator public ccipLocalSimulator; 16 | 17 | uint64 chainSelector; 18 | BurnMintERC677Helper ccipBnM; 19 | 20 | function setUp() public { 21 | ccipLocalSimulator = new CCIPLocalSimulator(); 22 | (uint64 chainSelector_, IRouterClient sourceRouter,,, LinkToken linkToken, BurnMintERC677Helper ccipBnM_,) = 23 | ccipLocalSimulator.configuration(); 24 | 25 | tokenTransferor = new TokenTransferor(address(sourceRouter), address(linkToken)); 26 | 27 | chainSelector = chainSelector_; 28 | ccipBnM = ccipBnM_; 29 | } 30 | 31 | function testTokenTransfer() public { 32 | uint256 amountToSend = 0.001 ether; 33 | uint256 amountForFees = 1 ether; 34 | address receiver = msg.sender; 35 | 36 | ccipBnM.drip(address(tokenTransferor)); 37 | 38 | ccipLocalSimulator.requestLinkFromFaucet(address(tokenTransferor), amountForFees); 39 | 40 | tokenTransferor.allowlistDestinationChain(chainSelector, true); 41 | 42 | uint256 receiverBalanceBefore = ccipBnM.balanceOf(receiver); 43 | uint256 tokenTransferorBalanceBefore = ccipBnM.balanceOf(address(tokenTransferor)); 44 | 45 | tokenTransferor.transferTokensPayLINK(chainSelector, receiver, address(ccipBnM), amountToSend); 46 | 47 | uint256 receiverBalanceAfter = ccipBnM.balanceOf(receiver); 48 | uint256 tokenTransferorBalanceAfter = ccipBnM.balanceOf(address(tokenTransferor)); 49 | 50 | assertEq(receiverBalanceAfter, receiverBalanceBefore + amountToSend); 51 | assertEq(tokenTransferorBalanceAfter, tokenTransferorBalanceBefore - amountToSend); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/smoke/ccip/UnsafeTokenAndDataTransfer.spec.ts: -------------------------------------------------------------------------------- 1 | import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | 5 | describe("CCIPSender_Unsafe", function () { 6 | 7 | async function deploy() { 8 | const localSimulatorFactory = await ethers.getContractFactory("CCIPLocalSimulator"); 9 | const localSimulator = await localSimulatorFactory.deploy(); 10 | 11 | const config: { 12 | chainSelector_: bigint; 13 | sourceRouter_: string; 14 | destinationRouter_: string; 15 | wrappedNative_: string; 16 | linkToken_: string; 17 | ccipBnM_: string; 18 | ccipLnM_: string; 19 | } = await localSimulator.configuration(); 20 | 21 | const CCIPSender_UnsafeFactory = await ethers.getContractFactory("CCIPSender_Unsafe"); 22 | const CCIPSender_Unsafe = await CCIPSender_UnsafeFactory.deploy(config.linkToken_, config.sourceRouter_); 23 | 24 | const CCIPReceiver_UnsafeFactory = await ethers.getContractFactory("CCIPReceiver_Unsafe"); 25 | const CCIPReceiver_Unsafe = await CCIPReceiver_UnsafeFactory.deploy(config.destinationRouter_); 26 | 27 | const ccipBnMFactory = await ethers.getContractFactory("BurnMintERC677Helper"); 28 | const ccipBnM = ccipBnMFactory.attach(config.ccipBnM_); 29 | 30 | return { localSimulator, CCIPSender_Unsafe, CCIPReceiver_Unsafe, config, ccipBnM }; 31 | } 32 | 33 | it("should transfer Hello World and 100 CCIP_BnM tokens", async function () { 34 | const { CCIPSender_Unsafe, CCIPReceiver_Unsafe, config, ccipBnM } = await loadFixture(deploy); 35 | 36 | const ONE_ETHER = 1_000_000_000_000_000_000n; 37 | 38 | await ccipBnM.drip(CCIPSender_Unsafe.target); 39 | expect(await ccipBnM.totalSupply()).to.deep.equal(ONE_ETHER); 40 | 41 | const ccipSenderUnsafeBalanceBefore = await ccipBnM.balanceOf(CCIPSender_Unsafe.target); 42 | const ccipReceiverUnsafeBalanceBefore = await ccipBnM.balanceOf(CCIPReceiver_Unsafe.target); 43 | 44 | const textToSend = `Hello World`; 45 | const amountToSend = 100n; 46 | 47 | await CCIPSender_Unsafe.send(CCIPReceiver_Unsafe.target, textToSend, config.chainSelector_, config.ccipBnM_, amountToSend); 48 | 49 | const ccipSenderUnsafeBalanceAfter = await ccipBnM.balanceOf(CCIPSender_Unsafe.target); 50 | const ccipReceiverUnsafeBalanceAfter = await ccipBnM.balanceOf(CCIPReceiver_Unsafe.target); 51 | expect(ccipSenderUnsafeBalanceAfter).to.deep.equal(ccipSenderUnsafeBalanceBefore - amountToSend); 52 | expect(ccipReceiverUnsafeBalanceAfter).to.deep.equal(ccipReceiverUnsafeBalanceBefore + amountToSend); 53 | 54 | const received = await CCIPReceiver_Unsafe.text(); 55 | expect(received).to.equal(textToSend); 56 | }); 57 | }); 58 | 59 | -------------------------------------------------------------------------------- /test/smoke/ccip/UnsafeTokenAndDataTransfer.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {CCIPSender_Unsafe} from "../../../src/test/ccip/CCIPSender_Unsafe.sol"; 6 | import {CCIPReceiver_Unsafe} from "../../../src/test/ccip/CCIPReceiver_Unsafe.sol"; 7 | import {CCIPLocalSimulator, IRouterClient, LinkToken, BurnMintERC677Helper} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 8 | 9 | contract UnsafeTokenAndDataTransferTest is Test { 10 | CCIPSender_Unsafe public sender; 11 | CCIPReceiver_Unsafe public receiver; 12 | 13 | uint64 chainSelector; 14 | BurnMintERC677Helper ccipBnM; 15 | 16 | function setUp() public { 17 | CCIPLocalSimulator ccipLocalSimulator = new CCIPLocalSimulator(); 18 | 19 | ( 20 | uint64 chainSelector_, 21 | IRouterClient sourceRouter_, 22 | IRouterClient destinationRouter_, 23 | , 24 | LinkToken linkToken_, 25 | BurnMintERC677Helper ccipBnM_, 26 | 27 | ) = ccipLocalSimulator.configuration(); 28 | 29 | chainSelector = chainSelector_; 30 | ccipBnM = ccipBnM_; 31 | address sourceRouter = address(sourceRouter_); 32 | address linkToken = address(linkToken_); 33 | address destinationRouter = address(destinationRouter_); 34 | 35 | sender = new CCIPSender_Unsafe(linkToken, sourceRouter); 36 | receiver = new CCIPReceiver_Unsafe(destinationRouter); 37 | } 38 | 39 | function testSend() public { 40 | ccipBnM.drip(address(sender)); // 1e18 41 | assertEq(ccipBnM.totalSupply(), 1 ether); 42 | 43 | string memory textToSend = "Hello World"; 44 | uint256 amountToSend = 100; 45 | 46 | bytes32 messageId = sender.send( 47 | address(receiver), 48 | textToSend, 49 | chainSelector, 50 | address(ccipBnM), 51 | amountToSend 52 | ); 53 | console2.logBytes32(messageId); 54 | 55 | string memory receivedText = receiver.text(); 56 | 57 | assertEq(receivedText, textToSend); 58 | 59 | assertEq(ccipBnM.balanceOf(address(sender)), 1 ether - amountToSend); 60 | assertEq(ccipBnM.balanceOf(address(receiver)), amountToSend); 61 | assertEq(ccipBnM.totalSupply(), 1 ether); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/smoke/data-feeds/BasicDataConsumerV3.spec.ts: -------------------------------------------------------------------------------- 1 | import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; 2 | import { ethers } from "hardhat"; 3 | import { expect } from "chai"; 4 | 5 | describe("BasicDataConsumerV3", function () { 6 | 7 | async function deploy() { 8 | const decimals = 8; 9 | const initialAnswer = 100000000000; 10 | 11 | const mockV3AggregatorFactory = await ethers.getContractFactory("MockV3Aggregator"); 12 | const mockV3Aggregator = await mockV3AggregatorFactory.deploy(decimals, initialAnswer); 13 | 14 | const basicDataConsumerV3Factory = await ethers.getContractFactory("BasicDataConsumerV3"); 15 | const basicDataConsumerV3 = await basicDataConsumerV3Factory.deploy(mockV3Aggregator.target); 16 | 17 | return { initialAnswer, basicDataConsumerV3 }; 18 | } 19 | 20 | it("should return answer from mock aggregator", async function () { 21 | const { initialAnswer, basicDataConsumerV3 } = await loadFixture(deploy); 22 | 23 | const answer = await basicDataConsumerV3.getChainlinkDataFeedLatestAnswer(); 24 | 25 | expect(answer).to.deep.equal(initialAnswer); 26 | }); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /test/smoke/data-feeds/BasicDataConsumerV3.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {MockV3Aggregator} from "@chainlink/local/src/data-feeds/MockV3Aggregator.sol"; 6 | 7 | import {BasicDataConsumerV3} from "../../../src/test/data-feeds/BasicDataConsumerV3.sol"; 8 | 9 | contract BasicDataConsumerV3Test is Test { 10 | BasicDataConsumerV3 public consumer; 11 | MockV3Aggregator public mockEthUsdAggregator; 12 | 13 | uint8 public decimals; 14 | int256 public initialAnswer; 15 | 16 | function setUp() public { 17 | decimals = 8; 18 | initialAnswer = 100000000000; 19 | mockEthUsdAggregator = new MockV3Aggregator(decimals, initialAnswer); 20 | consumer = new BasicDataConsumerV3(address(mockEthUsdAggregator)); 21 | } 22 | 23 | function test_smoke() public { 24 | int256 answer = consumer.getChainlinkDataFeedLatestAnswer(); 25 | assertEq(answer, initialAnswer, "answer should be equal to initialAnswer"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/smoke/data-streams/ChainlinkDataStreamProvider.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import { 6 | DataStreamsLocalSimulator, 7 | MockVerifierProxy, 8 | MockFeeManager, 9 | LinkToken 10 | } from "@chainlink/local/src/data-streams/DataStreamsLocalSimulator.sol"; 11 | import {MockReportGenerator} from "@chainlink/local/src/data-streams/MockReportGenerator.sol"; 12 | 13 | import {ChainlinkDataStreamProvider} from "../../../src/test/data-streams/ChainlinkDataStreamProvider.sol"; 14 | 15 | contract ChainlinkDataStreamProviderTest is Test { 16 | DataStreamsLocalSimulator public dataStreamsLocalSimulator; 17 | MockReportGenerator public mockReportGenerator; 18 | MockFeeManager public mockFeeManager; 19 | 20 | ChainlinkDataStreamProvider public consumer; 21 | int192 public initialPrice; 22 | 23 | function setUp() public { 24 | dataStreamsLocalSimulator = new DataStreamsLocalSimulator(); 25 | (, LinkToken linkToken_,, MockVerifierProxy mockVerifierProxy_, MockFeeManager mockFeeManager_,) = 26 | dataStreamsLocalSimulator.configuration(); 27 | 28 | mockFeeManager = mockFeeManager_; 29 | 30 | initialPrice = 1 ether; 31 | mockReportGenerator = new MockReportGenerator(initialPrice); 32 | 33 | consumer = new ChainlinkDataStreamProvider(address(mockVerifierProxy_), address(linkToken_)); 34 | } 35 | 36 | function test_smoke() public { 37 | mockFeeManager.setMockDiscount(address(consumer), 1e18); // 1e18 => 100% discount on fees 38 | 39 | int192 wantPrice = 200; 40 | int192 wantBid = 100; 41 | int192 wantAsk = 300; 42 | mockReportGenerator.updatePriceBidAndAsk(wantPrice, wantBid, wantAsk); 43 | 44 | bytes32 feedId = hex"0003777777777777777777777777777777777777777777777777777777777777"; 45 | address token = 0x7777777777777777777777777777777777777777; 46 | consumer.setBytes32(token, feedId); 47 | 48 | (bytes memory signedReportV3,) = mockReportGenerator.generateReportV3(); 49 | 50 | (address gotToken, int192 gotBid, int192 gotAsk, /*uint32 gotObservationsTimestamp*/ ) = 51 | consumer.getOraclePrice(token, signedReportV3); 52 | 53 | assertEq(gotToken, token); 54 | assertEq(gotBid, wantBid); 55 | assertEq(gotAsk, wantAsk); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/smoke/data-streams/ClientReportsVerifier.spec.ts: -------------------------------------------------------------------------------- 1 | import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; 2 | import { ethers } from "hardhat"; 3 | import { assert } from "chai"; 4 | import { MockReportGenerator } from "../../../scripts/data-streams/MockReportGenerator"; 5 | 6 | describe("ClientReportsVerifier", function () { 7 | 8 | async function deploy() { 9 | const localSimulatorFactory = await ethers.getContractFactory("DataStreamsLocalSimulator"); 10 | const localSimulator = await localSimulatorFactory.deploy(); 11 | 12 | const config: { 13 | wrappedNative_: string; 14 | linkToken_: string; 15 | mockVerifier_: string; 16 | mockVerifierProxy_: string; 17 | mockFeeManager_: string; 18 | mockRewardManager_: string; 19 | } = await localSimulator.configuration(); 20 | 21 | const initialPrice = ethers.parseEther("1"); 22 | const mockReportGenerator = new MockReportGenerator(initialPrice); 23 | 24 | const consumerFactory = await ethers.getContractFactory("ClientReportsVerifier"); 25 | const consumer = await consumerFactory.deploy(config.mockVerifierProxy_); 26 | 27 | await mockReportGenerator.updateFees(ethers.parseEther("1"), ethers.parseEther("0.5")); 28 | 29 | await localSimulator.requestLinkFromFaucet(consumer.target, ethers.parseEther("1")); 30 | 31 | // const mockFeeManager = await ethers.getContractAt("MockFeeManager", config.mockFeeManager_); 32 | // await mockFeeManager.setMockDiscount(consumer.target, ethers.parseEther("1")); // 1e18 => 100% discount on fees 33 | 34 | return { consumer, initialPrice, mockReportGenerator }; 35 | } 36 | 37 | it("should verify Data Streams report", async function () { 38 | const { consumer, initialPrice, mockReportGenerator } = await loadFixture(deploy); 39 | 40 | const { signedReport, report } = await mockReportGenerator.generateReportV3(); 41 | 42 | await consumer.verifyReport(signedReport); 43 | 44 | const lastDecodedPrice = await consumer.lastDecodedPrice(); 45 | assert(lastDecodedPrice === initialPrice); 46 | }); 47 | }); 48 | 49 | -------------------------------------------------------------------------------- /test/smoke/data-streams/ClientReportsVerifier.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import { 6 | DataStreamsLocalSimulator, 7 | MockVerifierProxy 8 | } from "@chainlink/local/src/data-streams/DataStreamsLocalSimulator.sol"; 9 | import {MockReportGenerator} from "@chainlink/local/src/data-streams/MockReportGenerator.sol"; 10 | 11 | import {ClientReportsVerifier} from "../../../src/test/data-streams/ClientReportsVerifier.sol"; 12 | 13 | contract ClientReportsVerifierTest is Test { 14 | DataStreamsLocalSimulator public dataStreamsLocalSimulator; 15 | MockReportGenerator public mockReportGenerator; 16 | 17 | ClientReportsVerifier public consumer; 18 | int192 initialPrice; 19 | 20 | function setUp() public { 21 | dataStreamsLocalSimulator = new DataStreamsLocalSimulator(); 22 | (,,, MockVerifierProxy mockVerifierProxy_,,) = dataStreamsLocalSimulator.configuration(); 23 | 24 | initialPrice = 1 ether; 25 | mockReportGenerator = new MockReportGenerator(initialPrice); 26 | 27 | consumer = new ClientReportsVerifier(address(mockVerifierProxy_)); 28 | } 29 | 30 | function test_smoke() public { 31 | mockReportGenerator.updateFees(1 ether, 0.5 ether); 32 | (bytes memory signedReportV3,) = mockReportGenerator.generateReportV3(); 33 | 34 | dataStreamsLocalSimulator.requestLinkFromFaucet(address(consumer), 1 ether); 35 | 36 | consumer.verifyReport(signedReportV3); 37 | 38 | int192 lastDecodedPrice = consumer.lastDecodedPrice(); 39 | assertEq(lastDecodedPrice, initialPrice); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/smoke/data-streams/ERC7412Compatible.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import { 6 | DataStreamsLocalSimulator, 7 | MockVerifierProxy 8 | } from "@chainlink/local/src/data-streams/DataStreamsLocalSimulator.sol"; 9 | import {MockReportGenerator} from "@chainlink/local/src/data-streams/MockReportGenerator.sol"; 10 | 11 | import {DataStreamsERC7412Compatible} from "../../../src/test/data-streams/ERC7412Compatible.sol"; 12 | 13 | contract ERC7412CompatibleTest is Test { 14 | DataStreamsLocalSimulator public dataStreamsLocalSimulator; 15 | MockReportGenerator public mockReportGenerator; 16 | 17 | DataStreamsERC7412Compatible public consumer; 18 | 19 | string public constant STRING_DATASTREAMS_FEEDLABEL = "feedIDs"; 20 | string public constant STRING_DATASTREAMS_QUERYLABEL = "timestamp"; 21 | 22 | function setUp() public { 23 | dataStreamsLocalSimulator = new DataStreamsLocalSimulator(); 24 | (,,, MockVerifierProxy mockVerifierProxy_,,) = dataStreamsLocalSimulator.configuration(); 25 | 26 | int192 initialPrice = 1 ether; 27 | mockReportGenerator = new MockReportGenerator(initialPrice); 28 | 29 | consumer = new DataStreamsERC7412Compatible(address(mockVerifierProxy_)); 30 | } 31 | 32 | function test_smoke() public { 33 | mockReportGenerator.updateFees(1 ether, 0.5 ether); 34 | dataStreamsLocalSimulator.requestLinkFromFaucet(address(consumer), 0.5 ether); 35 | 36 | bytes32 feedId = hex"0002777777777777777777777777777777777777777777777777777777777777"; 37 | uint32 stalenessTolerance = 5 minutes; 38 | 39 | try consumer.generate7412CompatibleCall(feedId, stalenessTolerance) {} 40 | catch (bytes memory lowLevelData) { 41 | if (bytes4(abi.encodeWithSignature("OracleDataRequired(address,bytes)")) == bytes4(lowLevelData)) { 42 | uint256 length = lowLevelData.length; 43 | bytes memory revertData = new bytes(length - 4); 44 | for (uint256 i = 4; i < length; ++i) { 45 | revertData[i - 4] = lowLevelData[i]; 46 | } 47 | 48 | (address oracleContract, bytes memory oracleQuery) = abi.decode(revertData, (address, bytes)); 49 | assertEq(oracleContract, address(consumer)); 50 | 51 | (, bytes32 revertedFeedId,,,) = abi.decode(oracleQuery, (string, bytes32, string, uint256, string)); 52 | assertEq(feedId, revertedFeedId); 53 | 54 | (bytes memory signedReportV2,) = mockReportGenerator.generateReportV2(); 55 | 56 | consumer.fulfillOracleQuery(signedReportV2); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/unit/ccip/CCIPLocalSimulatorUnit.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {CCIPLocalSimulator, IRouterClient} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol"; 6 | 7 | import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; 8 | import {ERC20} from 9 | "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/ERC20.sol"; 10 | import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; 11 | import {AccessControl} from 12 | "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v5.0.2/contracts/access/AccessControl.sol"; 13 | 14 | contract MockERC20TokenOwner is ERC20, OwnerIsCreator { 15 | constructor() ERC20("MockERC20Token", "MTK") {} 16 | 17 | function mint(address account, uint256 amount) public onlyOwner { 18 | _mint(account, amount); 19 | } 20 | } 21 | 22 | contract MockERC20TokenGetCCIPAdmin is ERC20 { 23 | address immutable i_CCIPAdmin; 24 | 25 | constructor() ERC20("MockERC20Token", "MTK") { 26 | i_CCIPAdmin = msg.sender; 27 | } 28 | 29 | function mint(address account, uint256 amount) public { 30 | require(msg.sender == i_CCIPAdmin, "Only CCIP Admin can mint"); 31 | _mint(account, amount); 32 | } 33 | 34 | function getCCIPAdmin() public view returns (address) { 35 | return (i_CCIPAdmin); 36 | } 37 | } 38 | 39 | contract MockERC20AccessControl is ERC20, AccessControl { 40 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 41 | 42 | constructor() ERC20("MockERC20Token", "MTK") { 43 | _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); 44 | } 45 | 46 | function mint(address account, uint256 amount) external { 47 | require(hasRole(MINTER_ROLE, msg.sender)); 48 | _mint(account, amount); 49 | } 50 | } 51 | 52 | contract CCIPLocalSimulatorUnitTest is Test { 53 | CCIPLocalSimulator public ccipLocalSimulator; 54 | IRouterClient public router; 55 | 56 | MockERC20TokenOwner public mockERC20TokenOwner; 57 | MockERC20TokenGetCCIPAdmin public mockERC20TokenGetCCIPAdmin; 58 | MockERC20AccessControl public mockERC20AccessControl; 59 | 60 | address alice; 61 | address bob; 62 | uint64 chainSelector; 63 | 64 | function setUp() public { 65 | ccipLocalSimulator = new CCIPLocalSimulator(); 66 | 67 | (uint64 chainSelector_, IRouterClient sourceRouter,,,,,) = ccipLocalSimulator.configuration(); 68 | chainSelector = chainSelector_; 69 | router = sourceRouter; 70 | 71 | alice = makeAddr("alice"); 72 | bob = makeAddr("bob"); 73 | 74 | vm.startPrank(alice); 75 | mockERC20TokenOwner = new MockERC20TokenOwner(); 76 | mockERC20TokenGetCCIPAdmin = new MockERC20TokenGetCCIPAdmin(); 77 | mockERC20AccessControl = new MockERC20AccessControl(); 78 | vm.stopPrank(); 79 | 80 | assertEq(mockERC20TokenOwner.owner(), alice); 81 | assertEq(mockERC20TokenGetCCIPAdmin.getCCIPAdmin(), alice); 82 | } 83 | 84 | function test_shouldSupportNewTokenIfCalledByOwner() public { 85 | address[] memory supportedTokensBefore = ccipLocalSimulator.getSupportedTokens(chainSelector); 86 | 87 | vm.startPrank(alice); 88 | ccipLocalSimulator.supportNewTokenViaOwner(address(mockERC20TokenOwner)); 89 | vm.stopPrank(); 90 | 91 | address[] memory supportedTokensAfter = ccipLocalSimulator.getSupportedTokens(chainSelector); 92 | assertEq(supportedTokensAfter.length, supportedTokensBefore.length + 1); 93 | assertEq(supportedTokensAfter[supportedTokensAfter.length - 1], address(mockERC20TokenOwner)); 94 | } 95 | 96 | function test_shouldRevertIfSupportNewTokenIsNotCalledByOwner() public { 97 | vm.startPrank(bob); 98 | vm.expectRevert( 99 | abi.encodeWithSelector(CCIPLocalSimulator.CCIPLocalSimulator__MsgSenderIsNotTokenOwner.selector) 100 | ); 101 | ccipLocalSimulator.supportNewTokenViaOwner(address(mockERC20TokenOwner)); 102 | vm.stopPrank(); 103 | } 104 | 105 | function test_shouldSupportNewTokenIfCalledByCCIPAdmin() public { 106 | address[] memory supportedTokensBefore = ccipLocalSimulator.getSupportedTokens(chainSelector); 107 | 108 | vm.startPrank(alice); 109 | ccipLocalSimulator.supportNewTokenViaGetCCIPAdmin(address(mockERC20TokenGetCCIPAdmin)); 110 | vm.stopPrank(); 111 | 112 | address[] memory supportedTokensAfter = ccipLocalSimulator.getSupportedTokens(chainSelector); 113 | assertEq(supportedTokensAfter.length, supportedTokensBefore.length + 1); 114 | assertEq(supportedTokensAfter[supportedTokensAfter.length - 1], address(mockERC20TokenGetCCIPAdmin)); 115 | } 116 | 117 | function test_shouldRevertIfSupportNewTokenIsNotCalledByCCIPAdmin() public { 118 | vm.startPrank(bob); 119 | vm.expectRevert( 120 | abi.encodeWithSelector(CCIPLocalSimulator.CCIPLocalSimulator__MsgSenderIsNotTokenOwner.selector) 121 | ); 122 | ccipLocalSimulator.supportNewTokenViaGetCCIPAdmin(address(mockERC20TokenGetCCIPAdmin)); 123 | vm.stopPrank(); 124 | } 125 | 126 | function test_shouldSupportNewTokenIfCalledByAccessControlDefaultAdmin() public { 127 | address[] memory supportedTokensBefore = ccipLocalSimulator.getSupportedTokens(chainSelector); 128 | 129 | vm.startPrank(alice); 130 | ccipLocalSimulator.supportNewTokenViaAccessControlDefaultAdmin(address(mockERC20AccessControl)); 131 | vm.stopPrank(); 132 | 133 | address[] memory supportedTokensAfter = ccipLocalSimulator.getSupportedTokens(chainSelector); 134 | assertEq(supportedTokensAfter.length, supportedTokensBefore.length + 1); 135 | assertEq(supportedTokensAfter[supportedTokensAfter.length - 1], address(mockERC20AccessControl)); 136 | } 137 | 138 | function test_shouldRevertIfSupportNewTokenIsNotCalledByAccessControlDefaultAdmin() public { 139 | vm.startPrank(bob); 140 | vm.expectRevert( 141 | abi.encodeWithSelector( 142 | CCIPLocalSimulator.CCIPLocalSimulator__RequiredRoleNotFound.selector, 143 | bob, 144 | mockERC20AccessControl.DEFAULT_ADMIN_ROLE(), 145 | address(mockERC20AccessControl) 146 | ) 147 | ); 148 | ccipLocalSimulator.supportNewTokenViaAccessControlDefaultAdmin(address(mockERC20AccessControl)); 149 | vm.stopPrank(); 150 | } 151 | 152 | function test_shouldSendCCIPMessageWithEvmExtraArgsV1() public { 153 | vm.startPrank(alice); 154 | Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ 155 | receiver: abi.encode(bob), 156 | data: "", 157 | tokenAmounts: new Client.EVMTokenAmount[](0), 158 | extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 500_000})), 159 | feeToken: address(0) 160 | }); 161 | router.ccipSend(chainSelector, message); 162 | vm.stopPrank(); 163 | } 164 | 165 | function test_shouldSendCCIPMessageWithEvmExtraArgsV2() public { 166 | vm.startPrank(alice); 167 | Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ 168 | receiver: abi.encode(bob), 169 | data: "", 170 | tokenAmounts: new Client.EVMTokenAmount[](0), 171 | extraArgs: Client._argsToBytes(Client.EVMExtraArgsV2({gasLimit: 500_000, allowOutOfOrderExecution: true})), 172 | feeToken: address(0) 173 | }); 174 | router.ccipSend(chainSelector, message); 175 | vm.stopPrank(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | --------------------------------------------------------------------------------