Add Oracle Aggregator and CCIP Integration

- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
This commit is contained in:
defiQUG
2025-12-12 14:57:48 -08:00
parent a1466e4005
commit 1fb7266469
1720 changed files with 241279 additions and 16 deletions

View File

@@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {Aggregator} from "../../contracts/oracle/Aggregator.sol";
import {CCIPRouter} from "../../contracts/ccip/CCIPRouter.sol";
import {CCIPSender} from "../../contracts/ccip/CCIPSender.sol";
import {CCIPReceiver} from "../../contracts/ccip/CCIPReceiver.sol";
contract ContractDeploymentTest is Test {
address public admin;
address public transmitter;
address public linkToken;
function setUp() public {
admin = address(this);
transmitter = address(0x123);
linkToken = address(0x456);
}
function testDeployAggregator() public {
Aggregator aggregator = new Aggregator(
"ETH/USD",
admin,
60,
50
);
assertEq(aggregator.description(), "ETH/USD");
assertEq(aggregator.admin(), admin);
assertEq(aggregator.heartbeat(), 60);
assertEq(aggregator.deviationThreshold(), 50);
}
function testDeployCCIPRouter() public {
uint256 baseFee = 1 ether;
uint256 dataFeePerByte = 1000;
CCIPRouter router = new CCIPRouter(linkToken, baseFee, dataFeePerByte);
assertEq(router.admin(), admin);
assertEq(router.baseFee(), baseFee);
assertEq(router.dataFeePerByte(), dataFeePerByte);
assertEq(router.feeToken(), linkToken);
}
function testDeployCCIPSender() public {
uint256 baseFee = 1 ether;
uint256 dataFeePerByte = 1000;
address router = address(new CCIPRouter(linkToken, baseFee, dataFeePerByte));
address aggregator = address(new Aggregator("ETH/USD", admin, 60, 50));
CCIPSender sender = new CCIPSender(router, aggregator, linkToken);
assertEq(address(sender.ccipRouter()), router);
assertEq(sender.oracleAggregator(), aggregator);
assertEq(sender.feeToken(), linkToken);
}
function testDeployCCIPReceiver() public {
uint256 baseFee = 1 ether;
uint256 dataFeePerByte = 1000;
address router = address(new CCIPRouter(linkToken, baseFee, dataFeePerByte));
address aggregator = address(new Aggregator("ETH/USD", admin, 60, 50));
CCIPReceiver receiver = new CCIPReceiver(router, aggregator);
assertEq(address(receiver.router()), router);
assertEq(receiver.oracleAggregator(), aggregator);
}
function testFullDeploymentFlow() public {
// Deploy aggregator
Aggregator aggregator = new Aggregator("ETH/USD", admin, 60, 50);
// Deploy CCIP router
uint256 baseFee = 1 ether;
uint256 dataFeePerByte = 1000;
CCIPRouter router = new CCIPRouter(linkToken, baseFee, dataFeePerByte);
// Deploy CCIP sender
CCIPSender sender = new CCIPSender(address(router), address(aggregator), linkToken);
// Deploy CCIP receiver
CCIPReceiver receiver = new CCIPReceiver(address(router), address(aggregator));
// Verify all contracts deployed
assertTrue(address(aggregator) != address(0));
assertTrue(address(router) != address(0));
assertTrue(address(sender) != address(0));
assertTrue(address(receiver) != address(0));
}
function testDeploymentWithConfiguration() public {
// Deploy with custom configuration
Aggregator aggregator = new Aggregator("BTC/USD", admin, 120, 100);
// Add transmitter
aggregator.addTransmitter(transmitter);
// Verify configuration
assertEq(aggregator.description(), "BTC/USD");
assertEq(aggregator.heartbeat(), 120);
assertEq(aggregator.deviationThreshold(), 100);
assertTrue(aggregator.isTransmitter(transmitter));
}
}

View File

@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {CCIPSender} from "../../contracts/ccip/CCIPSender.sol";
import {CCIPReceiver} from "../../contracts/ccip/CCIPReceiver.sol";
import {Aggregator} from "../../contracts/oracle/Aggregator.sol";
import {IRouterClient} from "../../contracts/ccip/IRouterClient.sol";
contract CrossChainOracleTest is Test {
CCIPSender public sender;
CCIPReceiver public receiver;
Aggregator public sourceAggregator;
Aggregator public destAggregator;
address public mockRouter;
address public linkToken;
uint64 constant SOURCE_CHAIN = 138;
uint64 constant DEST_CHAIN = 5009297550715157269;
function setUp() public {
mockRouter = address(new MockRouter());
linkToken = address(new MockLinkToken());
sourceAggregator = new Aggregator("ETH/USD", address(this), 60, 50);
destAggregator = new Aggregator("ETH/USD", address(this), 60, 50);
sender = new CCIPSender(mockRouter, address(sourceAggregator), linkToken);
receiver = new CCIPReceiver(mockRouter, address(destAggregator));
sourceAggregator.addTransmitter(address(this));
destAggregator.addTransmitter(address(receiver));
sender.addDestination(DEST_CHAIN, address(receiver));
// Mint LINK tokens to aggregator (since aggregator will pay fees)
MockLinkToken(linkToken).mint(address(sourceAggregator), 1000e18);
// Also mint to sender for fee calculations
MockLinkToken(linkToken).mint(address(sender), 1000e18);
}
function testCrossChainOracleSync() public {
uint256 price = 25000000000;
// Update source oracle (this should trigger CCIP send if using OracleWithCCIP)
sourceAggregator.updateAnswer(price);
// Approve sender to spend aggregator's LINK tokens
vm.prank(address(sourceAggregator));
MockLinkToken(linkToken).approve(address(sender), 1000e18);
// Send cross-chain update (must be called by aggregator)
vm.prank(address(sourceAggregator));
sender.sendOracleUpdate(DEST_CHAIN, price, 1, block.timestamp);
// Simulate message delivery
IRouterClient.Any2EVMMessage memory message = IRouterClient.Any2EVMMessage({
messageId: keccak256("test"),
sourceChainSelector: SOURCE_CHAIN,
sender: abi.encode(address(sender)),
data: abi.encode(price, uint256(1), block.timestamp),
tokenAmounts: new IRouterClient.TokenAmount[](0)
});
vm.prank(mockRouter);
receiver.ccipReceive(message);
// Verify destination oracle updated
(uint256 roundId, int256 answer, , , ) = destAggregator.latestRoundData();
assertEq(uint256(answer), price, "Destination price should match");
assertEq(roundId, 1, "Round ID should match");
}
}
contract MockRouter is IRouterClient {
function ccipSend(uint64, EVM2AnyMessage memory) external payable returns (bytes32, uint256) {
return (keccak256("mock"), 0.01e18);
}
function getFee(uint64, EVM2AnyMessage memory) external pure returns (uint256) {
return 0.01e18;
}
function getSupportedTokens(uint64) external pure returns (address[] memory) {
return new address[](0);
}
}
contract MockLinkToken {
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
function mint(address to, uint256 amount) external {
balanceOf[to] += amount;
}
function transfer(address to, uint256 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
require(balanceOf[from] >= amount, "Insufficient balance");
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
balanceOf[from] -= amount;
balanceOf[to] += amount;
allowance[from][msg.sender] -= amount;
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
return true;
}
}

View File

@@ -0,0 +1,140 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {Aggregator} from "../../contracts/oracle/Aggregator.sol";
import {CCIPSender} from "../../contracts/ccip/CCIPSender.sol";
contract NetworkResilienceTest is Test {
Aggregator public aggregator;
address public transmitter1;
address public transmitter2;
address public transmitter3;
function setUp() public {
aggregator = new Aggregator("ETH/USD", address(this), 60, 50);
transmitter1 = address(0x111);
transmitter2 = address(0x222);
transmitter3 = address(0x333);
aggregator.addTransmitter(transmitter1);
aggregator.addTransmitter(transmitter2);
aggregator.addTransmitter(transmitter3);
}
function testOracleContinuesWithOneTransmitterFailure() public {
uint256 price1 = 25000000000;
uint256 price2 = 25100000000;
// Transmitter 1 updates
vm.prank(transmitter1);
aggregator.updateAnswer(price1);
// Fast forward past heartbeat to create new round
vm.warp(block.timestamp + 61);
// Transmitter 1 fails, transmitter 2 continues with new round
vm.prank(transmitter2);
aggregator.updateAnswer(price2);
(uint256 roundId, int256 answer, , , ) = aggregator.latestRoundData();
assertEq(uint256(answer), price2, "Oracle should continue with remaining transmitters");
assertGt(roundId, 1, "Should create new round");
}
function testOracleHandlesMultipleTransmitterFailures() public {
uint256 price1 = 25000000000;
uint256 price2 = 25100000000;
uint256 price3 = 25200000000;
// All transmitters update in round 1
vm.prank(transmitter1);
aggregator.updateAnswer(price1);
vm.prank(transmitter2);
aggregator.updateAnswer(price1);
vm.prank(transmitter3);
aggregator.updateAnswer(price1);
// Fast forward past heartbeat to create new round
vm.warp(block.timestamp + 61);
// Two transmitters fail, one continues with new round
vm.prank(transmitter3);
aggregator.updateAnswer(price3);
(uint256 roundId, int256 answer, , , ) = aggregator.latestRoundData();
assertEq(uint256(answer), price3, "Oracle should continue with remaining transmitter");
assertGt(roundId, 1, "Should create new round");
}
function testOracleRecoversFromPause() public {
uint256 price1 = 25000000000;
uint256 price2 = 25100000000;
// Update before pause
vm.prank(transmitter1);
aggregator.updateAnswer(price1);
// Verify initial state
(uint256 roundId1, int256 answer1, , , ) = aggregator.latestRoundData();
assertEq(uint256(answer1), price1, "Initial price should be set");
// Pause oracle (must be called by admin)
aggregator.pause();
// Try to update (should fail)
vm.prank(transmitter1);
vm.expectRevert("Aggregator: paused");
aggregator.updateAnswer(price2);
// Unpause (must be called by admin)
aggregator.unpause();
// Update should work again
vm.prank(transmitter1);
aggregator.updateAnswer(price2);
(uint256 roundId2, int256 answer2, , , ) = aggregator.latestRoundData();
assertEq(uint256(answer2), price2, "Oracle should recover after unpause");
assertGt(roundId2, roundId1, "Should create new round after unpause");
}
function testOracleHandlesStaleData() public {
uint256 price1 = 25000000000;
// Initial update
vm.prank(transmitter1);
aggregator.updateAnswer(price1);
// Fast forward past heartbeat
vm.warp(block.timestamp + 61);
// New update should create new round
vm.prank(transmitter1);
aggregator.updateAnswer(price1);
(uint256 roundId, , , , ) = aggregator.latestRoundData();
assertGt(roundId, 1, "Should create new round after heartbeat");
}
function testOracleHandlesPriceDeviation() public {
uint256 price1 = 25000000000; // $250.00
uint256 price2 = 25125000000; // $251.25 (0.5% deviation)
// Initial update
vm.prank(transmitter1);
aggregator.updateAnswer(price1);
// Update with deviation
vm.prank(transmitter1);
aggregator.updateAnswer(price2);
(uint256 roundId, int256 answer, , , ) = aggregator.latestRoundData();
assertEq(uint256(answer), price2, "Oracle should accept price deviation");
assertGt(roundId, 1, "Should create new round on deviation");
}
}

81
test/e2e/OracleFlow.t.sol Normal file
View File

@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {Aggregator} from "../../contracts/oracle/Aggregator.sol";
import {OracleWithCCIP} from "../../contracts/oracle/OracleWithCCIP.sol";
import {CCIPSender} from "../../contracts/ccip/CCIPSender.sol";
contract OracleFlowTest is Test {
Aggregator public aggregator;
OracleWithCCIP public oracleWithCCIP;
CCIPSender public ccipSender;
address public transmitter;
function setUp() public {
transmitter = address(this);
aggregator = new Aggregator("ETH/USD", address(this), 60, 50);
aggregator.addTransmitter(transmitter);
// Deploy CCIP components (simplified for testing)
address mockRouter = address(0x123);
address mockFeeToken = address(0x456);
ccipSender = new CCIPSender(mockRouter, address(aggregator), mockFeeToken);
oracleWithCCIP = new OracleWithCCIP(
"ETH/USD",
address(this),
60,
50,
address(ccipSender)
);
oracleWithCCIP.addTransmitter(transmitter);
}
function testFullOracleUpdateFlow() public {
uint256 price = 25000000000; // $250.00
// Update oracle
oracleWithCCIP.updateAnswer(price);
// Verify update
(uint256 roundId, int256 answer, , , ) = oracleWithCCIP.latestRoundData();
assertEq(uint256(answer), price, "Price should match");
assertEq(roundId, 1, "Round ID should be 1");
}
function testOracleUpdateWithHeartbeat() public {
uint256 price1 = 25000000000;
uint256 price2 = 25100000000;
// First update
oracleWithCCIP.updateAnswer(price1);
// Fast forward past heartbeat
vm.warp(block.timestamp + 61);
// Second update should create new round
oracleWithCCIP.updateAnswer(price2);
(uint256 roundId, int256 answer, , , ) = oracleWithCCIP.latestRoundData();
assertEq(uint256(answer), price2, "Price should be updated");
assertEq(roundId, 2, "Round ID should increment");
}
function testOracleUpdateWithDeviation() public {
uint256 price1 = 25000000000; // $250.00
uint256 price2 = 25125000000; // $251.25 (0.5% deviation)
// First update
oracleWithCCIP.updateAnswer(price1);
// Second update with deviation
oracleWithCCIP.updateAnswer(price2);
(uint256 roundId, int256 answer, , , ) = oracleWithCCIP.latestRoundData();
assertEq(uint256(answer), price2, "Price should be updated");
assertGt(roundId, 1, "Round ID should increment on deviation");
}
}