From c9613384bc8152a369a331b5bbc99b95128f8d1a Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Tue, 8 Oct 2024 08:52:59 +0200 Subject: [PATCH 01/10] Add redstone-oracles-monorepo submodule --- .gitmodules | 3 +++ lib/redstone-oracles-monorepo | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/redstone-oracles-monorepo diff --git a/.gitmodules b/.gitmodules index feb437a3..7a875bb4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/openzeppelin-foundry-upgrades"] path = lib/openzeppelin-foundry-upgrades url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "lib/redstone-oracles-monorepo"] + path = lib/redstone-oracles-monorepo + url = https://github.com/redstone-finance/redstone-oracles-monorepo diff --git a/lib/redstone-oracles-monorepo b/lib/redstone-oracles-monorepo new file mode 160000 index 00000000..143ac688 --- /dev/null +++ b/lib/redstone-oracles-monorepo @@ -0,0 +1 @@ +Subproject commit 143ac6885dd74b0a8be3776e45e7cfcd3713fb6e From d74f42ef9c999f9b4b963d4e5e84229a50b87be8 Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Tue, 8 Oct 2024 09:25:42 +0200 Subject: [PATCH 02/10] forge install: chainlink v2.16.0 --- .gitmodules | 3 +++ lib/chainlink | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/chainlink diff --git a/.gitmodules b/.gitmodules index 7a875bb4..f4a73568 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "lib/redstone-oracles-monorepo"] path = lib/redstone-oracles-monorepo url = https://github.com/redstone-finance/redstone-oracles-monorepo +[submodule "lib/chainlink"] + path = lib/chainlink + url = https://github.com/smartcontractkit/chainlink diff --git a/lib/chainlink b/lib/chainlink new file mode 160000 index 00000000..72a7d232 --- /dev/null +++ b/lib/chainlink @@ -0,0 +1 @@ +Subproject commit 72a7d232412f3f5602e5e6d73959615c3b6ba1c0 From 0f0147b688a692ac84bcd4b1639c30ce9923cd73 Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Tue, 8 Oct 2024 15:01:05 +0200 Subject: [PATCH 03/10] Use Chainlink v2.7.2 module to be compatible with RedStone module version --- lib/chainlink | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chainlink b/lib/chainlink index 72a7d232..3cf93c9f 160000 --- a/lib/chainlink +++ b/lib/chainlink @@ -1 +1 @@ -Subproject commit 72a7d232412f3f5602e5e6d73959615c3b6ba1c0 +Subproject commit 3cf93c9f849be99a451ddd77ac203395760302c2 From f8870b68791313075dc25f18444bfc88cce94ed0 Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Wed, 16 Oct 2024 10:25:26 +0200 Subject: [PATCH 04/10] Implement Primary Prod Adapter and PriceFeed for USDT --- .env.devnet | 3 + .env.mainnet | 3 + .env.testnet | 3 + foundry.toml | 8 ++ ...10_deployMultiFeedAdaptersWithoutRounds.sh | 44 +++++++++ script/11_deployPriceFeedsWithoutRounds.sh | 44 +++++++++ ...iFeedAdapterWithoutRoundsPrimaryProd.s.sol | 89 +++++++++++++++++++ .../L2/L2PriceFeedLskWithoutRounds.s.sol | 40 +++++++++ .../L2/L2PriceFeedUsdtWithoutRounds.s.sol | 88 ++++++++++++++++++ script/contracts/Utils.sol | 45 ++++++++++ ...ltiFeedAdapterWithoutRoundsPrimaryProd.sol | 69 ++++++++++++++ src/L2/L2PriceFeedLskWithoutRounds.sol | 16 ++++ src/L2/L2PriceFeedUsdtWithoutRounds.sol | 47 ++++++++++ test/utils/Utils.t.sol | 4 + 14 files changed, 503 insertions(+) create mode 100755 script/10_deployMultiFeedAdaptersWithoutRounds.sh create mode 100755 script/11_deployPriceFeedsWithoutRounds.sh create mode 100644 script/contracts/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.s.sol create mode 100644 script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol create mode 100644 script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol create mode 100644 src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol create mode 100644 src/L2/L2PriceFeedLskWithoutRounds.sol create mode 100644 src/L2/L2PriceFeedUsdtWithoutRounds.sol diff --git a/.env.devnet b/.env.devnet index ba14dc76..1efdffc0 100644 --- a/.env.devnet +++ b/.env.devnet @@ -37,6 +37,9 @@ L2_VESTING_WALLET_OWNER_ADDRESS=0x # Airdrop wallet address where LSK tokens are transferred to after airdrop period ends L2_AIRDROP_WALLET_ADDRESS=0x +# Owner of L2MultiFeedAdapter* and L2PriceFeed* contracts +L2_ADAPTER_PRICEFEED_OWNER_ADDRESS=0x + # Salt for deterministic smart contract address generation DETERMINISTIC_ADDRESS_SALT="lisk_l2_token_salt_dev_network" diff --git a/.env.mainnet b/.env.mainnet index e0e611bc..64e9879f 100644 --- a/.env.mainnet +++ b/.env.mainnet @@ -37,6 +37,9 @@ L2_AIRDROP_OWNER_ADDRESS=0x394Ae9d48eeca1C69a989B5A8C787081595c55A7 # Airdrop wallet address where LSK tokens are transferred to after airdrop period ends L2_AIRDROP_WALLET_ADDRESS=0x0c92121A7C15cF01041e1122483Bc173Baccd877 +# Owner of L2MultiFeedAdapter* and L2PriceFeed* contracts +L2_ADAPTER_PRICEFEED_OWNER_ADDRESS=0x394Ae9d48eeca1C69a989B5A8C787081595c55A7 + # Salt for deterministic smart contract address generation DETERMINISTIC_ADDRESS_SALT="lisk_l2_token_deterministic_salt" diff --git a/.env.testnet b/.env.testnet index 41620de8..913dff9c 100644 --- a/.env.testnet +++ b/.env.testnet @@ -37,6 +37,9 @@ L2_VESTING_WALLET_OWNER_ADDRESS=0x84277C9255b3B6904aCfe12C2c45c6a36E81B059 # Airdrop wallet address where LSK tokens are transferred to after airdrop period ends L2_AIRDROP_WALLET_ADDRESS=0xaF3f4B5033Ef666b3F3EA91a0f360541cC21e5d7 +# Owner of L2MultiFeedAdapter* and L2PriceFeed* contracts +L2_ADAPTER_PRICEFEED_OWNER_ADDRESS=0x84277C9255b3B6904aCfe12C2c45c6a36E81B059 + # Salt for deterministic smart contract address generation DETERMINISTIC_ADDRESS_SALT="lisk_l2_token_salt_test_network" diff --git a/foundry.toml b/foundry.toml index aac1505e..988a467a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,7 @@ solc_version = "0.8.23" optimizer = true optimizer_runs = 200 remappings = [ + '@openzeppelin/contracts-upgradeable/proxy/=lib/openzeppelin-contracts-upgradeable/contracts/proxy/', '@openzeppelin/=lib/openzeppelin-contracts/', '@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/', 'ds-test/=lib/forge-std/lib/ds-test/src/', @@ -19,6 +20,13 @@ remappings = [ '@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/', 'openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/', 'solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/', + 'ERC4626/=lib/properties/lib/ERC4626/contracts/', + 'properties/=lib/properties/contracts/', + 'redstone-oracles-monorepo/=lib/redstone-oracles-monorepo/', + '@redstone-finance/=lib/redstone-oracles-monorepo/packages/', + 'solmate/=lib/properties/lib/solmate/src/', + '@chainlink=lib/chainlink/', + 'chainlink/=lib/chainlink/', ] deny_warnings = true ignored_warnings_from = [ diff --git a/script/10_deployMultiFeedAdaptersWithoutRounds.sh b/script/10_deployMultiFeedAdaptersWithoutRounds.sh new file mode 100755 index 00000000..bb02590a --- /dev/null +++ b/script/10_deployMultiFeedAdaptersWithoutRounds.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +echo "Instructing the shell to exit immediately if any command returns a non-zero exit status..." +set -e +echo "Done." + +echo "Navigating to the root directory of the project..." +cd ../ +echo "Done." + +echo "Setting environment variables..." +source .env +echo "Done." + +echo "Creating $NETWORK directory inside deployment/artifacts/contracts directory..." +if [ -z "$NETWORK" ] +then + echo "NETWORK variable inside .env file is not set. Please set NETWORK environment variable." + exit 1 +else + if [ -d "deployment/artifacts/contracts/$NETWORK" ] + then + echo "Directory deployment/artifacts/contracts/$NETWORK already exists." + else + mkdir deployment/artifacts/contracts/$NETWORK + fi +fi +echo "Done." + +echo "Deploying and if enabled verifying L2MultiFeedAdapterWithoutRoundsPrimaryProd smart contract..." +if [ -z "$CONTRACT_VERIFIER" ] +then + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.s.sol:L2MultiFeedAdapterWithoutRoundsPrimaryProdScript +else + if [ $CONTRACT_VERIFIER = "blockscout" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.s.sol:L2MultiFeedAdapterWithoutRoundsPrimaryProdScript + fi + if [ $CONTRACT_VERIFIER = "etherscan" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.s.sol:L2MultiFeedAdapterWithoutRoundsPrimaryProdScript + fi +fi +echo "Done." diff --git a/script/11_deployPriceFeedsWithoutRounds.sh b/script/11_deployPriceFeedsWithoutRounds.sh new file mode 100755 index 00000000..3ec8f841 --- /dev/null +++ b/script/11_deployPriceFeedsWithoutRounds.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +echo "Instructing the shell to exit immediately if any command returns a non-zero exit status..." +set -e +echo "Done." + +echo "Navigating to the root directory of the project..." +cd ../ +echo "Done." + +echo "Setting environment variables..." +source .env +echo "Done." + +echo "Creating $NETWORK directory inside deployment/artifacts/contracts directory..." +if [ -z "$NETWORK" ] +then + echo "NETWORK variable inside .env file is not set. Please set NETWORK environment variable." + exit 1 +else + if [ -d "deployment/artifacts/contracts/$NETWORK" ] + then + echo "Directory deployment/artifacts/contracts/$NETWORK already exists." + else + mkdir deployment/artifacts/contracts/$NETWORK + fi +fi +echo "Done." + +echo "Deploying and if enabled verifying L2PriceFeedUsdtWithoutRounds smart contract..." +if [ -z "$CONTRACT_VERIFIER" ] +then + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol:L2PriceFeedUsdtWithoutRoundsScript +else + if [ $CONTRACT_VERIFIER = "blockscout" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol:L2PriceFeedUsdtWithoutRoundsScript + fi + if [ $CONTRACT_VERIFIER = "etherscan" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol:L2PriceFeedUsdtWithoutRoundsScript + fi +fi +echo "Done." diff --git a/script/contracts/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.s.sol b/script/contracts/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.s.sol new file mode 100644 index 00000000..95bfca13 --- /dev/null +++ b/script/contracts/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.s.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Script, console2 } from "forge-std/Script.sol"; +import { L2MultiFeedAdapterWithoutRoundsPrimaryProd } from "src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol"; +import "script/contracts/Utils.sol"; + +/// @title L2MultiFeedAdapterWithoutRoundsPrimaryProdScript - L2MultiFeedAdapterWithoutRoundsPrimaryProd deployment +/// script +/// @notice This contract is used to deploy L2MultiFeedAdapterWithoutRoundsPrimaryProd contract. +contract L2MultiFeedAdapterWithoutRoundsPrimaryProdScript is Script { + /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. + Utils utils; + + function setUp() public { + utils = new Utils(); + } + + /// @notice This function deploys L2MultiFeedAdapterWithoutRoundsPrimaryProd contract. + function run() public { + // Deployer's private key. Owner of the L2MultiFeedAdapterWithoutRoundsPrimaryProd. PRIVATE_KEY is set in .env + // file. + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + console2.log("Deploying L2MultiFeedAdapterWithoutRoundsPrimaryProd contract..."); + + // owner Address, the ownership of L2MultiFeedAdapterWithoutRoundsPrimaryProd proxy contract is transferred to + // after deployment + address ownerAddress = vm.envAddress("L2_ADAPTER_PRICEFEED_OWNER_ADDRESS"); + assert(ownerAddress != address(0)); + console2.log( + "L2 MultiFeed Adapter Without Rounds PrimaryProd contract owner address: %s (after ownership will be accepted)", + ownerAddress + ); + + // deploy L2MultiFeedAdapterWithoutRoundsPrimaryProd implementation contract + vm.startBroadcast(deployerPrivateKey); + L2MultiFeedAdapterWithoutRoundsPrimaryProd l2AdapterImplementation = + new L2MultiFeedAdapterWithoutRoundsPrimaryProd(); + vm.stopBroadcast(); + + assert(address(l2AdapterImplementation) != address(0)); + + // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + assert( + l2AdapterImplementation.proxiableUUID() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) + ); + + // deploy L2MultiFeedAdapterWithoutRoundsPrimaryProd proxy contract and at the same time initialize the proxy + // contract (calls the initialize function in L2MultiFeedAdapterWithoutRoundsPrimaryProd) + vm.startBroadcast(deployerPrivateKey); + ERC1967Proxy l2AdapterProxy = new ERC1967Proxy( + address(l2AdapterImplementation), abi.encodeWithSelector(l2AdapterImplementation.initialize.selector) + ); + vm.stopBroadcast(); + assert(address(l2AdapterProxy) != address(0)); + + // wrap in ABI to support easier calls + L2MultiFeedAdapterWithoutRoundsPrimaryProd l2Adapter = + L2MultiFeedAdapterWithoutRoundsPrimaryProd(address(l2AdapterProxy)); + assert(l2Adapter.getUniqueSignersThreshold() == 2); + assert(l2Adapter.getAuthorisedSignerIndex(0x8BB8F32Df04c8b654987DAaeD53D6B6091e3B774) == 0); + assert(l2Adapter.getAuthorisedSignerIndex(0xdEB22f54738d54976C4c0fe5ce6d408E40d88499) == 1); + assert(l2Adapter.getAuthorisedSignerIndex(0x51Ce04Be4b3E32572C4Ec9135221d0691Ba7d202) == 2); + assert(l2Adapter.getAuthorisedSignerIndex(0xDD682daEC5A90dD295d14DA4b0bec9281017b5bE) == 3); + assert(l2Adapter.getAuthorisedSignerIndex(0x9c5AE89C4Af6aA32cE58588DBaF90d18a855B6de) == 4); + + // transfer ownership of L2MultiFeedAdapterWithoutRoundsPrimaryProd proxy; because of using + // Ownable2StepUpgradeable contract, new owner has to accept ownership + vm.startBroadcast(deployerPrivateKey); + l2Adapter.transferOwnership(ownerAddress); + vm.stopBroadcast(); + assert(l2Adapter.owner() == vm.addr(deployerPrivateKey)); // ownership is not yet accepted + + console2.log("L2 MultiFeed Adapter Without Rounds PrimaryProd contract successfully deployed!"); + console2.log("L2 MultiFeed Adapter (Implementation) address: %s", address(l2AdapterImplementation)); + console2.log("L2 MultiFeed Adapter (Proxy) address: %s", address(l2Adapter)); + console2.log( + "Owner of L2 MultiFeed Adapter (Proxy) address: %s (after ownership will be accepted)", ownerAddress + ); + + // write L2MultiFeedAdapterWithoutRoundsPrimaryProd address to l2addresses.json + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation = address(l2AdapterImplementation); + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd = address(l2Adapter); + utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); + } +} diff --git a/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol b/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol new file mode 100644 index 00000000..0bccf8cc --- /dev/null +++ b/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { Script, console2 } from "forge-std/Script.sol"; +import { L2PriceFeedLskWithoutRounds } from "src/L2/L2PriceFeedLskWithoutRounds.sol"; +import "script/contracts/Utils.sol"; + +/// @title L2PriceFeedLskWithoutRoundsScript - L2PriceFeedLskWithoutRounds deployment script +/// @notice This contract is used to deploy L2PriceFeedLskWithoutRounds contract. +contract L2PriceFeedLskWithoutRoundsScript is Script { + /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. + Utils utils; + + function setUp() public { + utils = new Utils(); + } + + /// @notice This function deploys L2PriceFeedLskWithoutRounds contract. + function run() public { + // Deployer's private key. Owner of the L2PriceFeedLskWithoutRounds. PRIVATE_KEY is set in .env file. + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + console2.log("Deploying L2PriceFeedLskWithoutRounds contract..."); + + // deploy L2PriceFeedLskWithoutRounds contract + vm.startBroadcast(deployerPrivateKey); + L2PriceFeedLskWithoutRounds l2PriceFeedLskWithoutRounds = new L2PriceFeedLskWithoutRounds(); + vm.stopBroadcast(); + + assert(address(l2PriceFeedLskWithoutRounds) != address(0)); + //assert(l2PriceFeedLskWithoutRounds.l2LiskTokenAddress() == l2AddressesConfig.L2LiskToken); + + console2.log("L2PriceFeedLskWithoutRounds successfully deployed!"); + console2.log("L2PriceFeedLskWithoutRounds address: %s", address(l2PriceFeedLskWithoutRounds)); + + // write L2PriceFeedLskWithoutRounds address to l2addresses.json + //l2AddressesConfig.L2PriceFeedLskWithoutRounds = address(l2PriceFeedLskWithoutRounds); + //utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); + } +} diff --git a/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol b/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol new file mode 100644 index 00000000..e7f3ece4 --- /dev/null +++ b/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Script, console2 } from "forge-std/Script.sol"; +import { L2PriceFeedUsdtWithoutRounds } from "src/L2/L2PriceFeedUsdtWithoutRounds.sol"; +import "script/contracts/Utils.sol"; + +/// @title L2PriceFeedUsdtWithoutRoundsScript - L2PriceFeedUsdtWithoutRounds deployment +/// script +/// @notice This contract is used to deploy L2PriceFeedUsdtWithoutRounds contract. +contract L2PriceFeedUsdtWithoutRoundsScript is Script { + /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. + Utils utils; + + function setUp() public { + utils = new Utils(); + } + + /// @notice This function deploys L2PriceFeedUsdtWithoutRounds contract. + function run() public { + // Deployer's private key. Owner of the L2PriceFeedUsdtWithoutRounds. PRIVATE_KEY is set in .env + // file. + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + console2.log("Deploying L2PriceFeedUsdtWithoutRounds contract..."); + + // owner Address, the ownership of L2PriceFeedUsdtWithoutRounds proxy contract is transferred to + // after deployment + address ownerAddress = vm.envAddress("L2_ADAPTER_PRICEFEED_OWNER_ADDRESS"); + assert(ownerAddress != address(0)); + console2.log( + "L2 PriceFeed USDT Without Rounds contract owner address: %s (after ownership will be accepted)", + ownerAddress + ); + + // deploy L2PriceFeedUsdtWithoutRounds implementation contract + vm.startBroadcast(deployerPrivateKey); + L2PriceFeedUsdtWithoutRounds l2PriceFeedImplementation = new L2PriceFeedUsdtWithoutRounds(); + vm.stopBroadcast(); + + assert(address(l2PriceFeedImplementation) != address(0)); + + // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + assert( + l2PriceFeedImplementation.proxiableUUID() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) + ); + + // deploy L2PriceFeedUsdtWithoutRounds proxy contract and at the same time initialize the proxy + // contract (calls the initialize function in L2PriceFeedUsdtWithoutRounds) + vm.startBroadcast(deployerPrivateKey); + ERC1967Proxy l2PriceFeedProxy = new ERC1967Proxy( + address(l2PriceFeedImplementation), abi.encodeWithSelector(l2PriceFeedImplementation.initialize.selector) + ); + vm.stopBroadcast(); + assert(address(l2PriceFeedProxy) != address(0)); + + // wrap in ABI to support easier calls + L2PriceFeedUsdtWithoutRounds l2PriceFeed = L2PriceFeedUsdtWithoutRounds(address(l2PriceFeedProxy)); + assert(l2PriceFeed.decimals() == 8); + assert(keccak256(bytes(l2PriceFeed.description())) == keccak256(bytes("Redstone Price Feed"))); + assert(l2PriceFeed.getDataFeedId() == bytes32("USDT")); + assert(address(l2PriceFeed.getPriceFeedAdapter()) == address(0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0)); + + // transfer ownership of L2PriceFeedUsdtWithoutRounds proxy; because of using + // Ownable2StepUpgradeable contract, new owner has to accept ownership + vm.startBroadcast(deployerPrivateKey); + l2PriceFeed.transferOwnership(ownerAddress); + vm.stopBroadcast(); + assert(l2PriceFeed.owner() == vm.addr(deployerPrivateKey)); // ownership is not yet accepted + + console2.log("L2 PriceFeed USDT Without Rounds contract successfully deployed!"); + console2.log( + "L2 PriceFeed USDT Without Rounds (Implementation) address: %s", address(l2PriceFeedImplementation) + ); + console2.log("L2 PriceFeed USDT Without Rounds (Proxy) address: %s", address(l2PriceFeed)); + console2.log( + "Owner of L2 PriceFeed USDT Without Rounds (Proxy) address: %s (after ownership will be accepted)", + ownerAddress + ); + + // write L2PriceFeedUsdtWithoutRounds address to l2addresses.json + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); + l2AddressesConfig.L2PriceFeedUsdtWithoutRoundsImplementation = address(l2PriceFeedImplementation); + l2AddressesConfig.L2PriceFeedUsdtWithoutRounds = address(l2PriceFeed); + utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); + } +} diff --git a/script/contracts/Utils.sol b/script/contracts/Utils.sol index c7436502..59242b86 100644 --- a/script/contracts/Utils.sol +++ b/script/contracts/Utils.sol @@ -43,6 +43,14 @@ contract Utils is Script { address L2LockingPositionImplementation; /// @notice L2 LockingPositionPaused address. address L2LockingPositionPaused; + /// @notice L2 MultiFeedAdapterWithoutRoundsPrimaryProd address. + address L2MultiFeedAdapterWithoutRoundsPrimaryProd; + /// @notice The current implementation of L2 MultiFeedAdapterWithoutRoundsPrimaryProd Contract. + address L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation; + /// @notice L2 PriceFeedUsdtWithoutRounds address. + address L2PriceFeedUsdtWithoutRounds; + /// @notice The current implementation of L2 PriceFeedUsdtWithoutRounds Contract. + address L2PriceFeedUsdtWithoutRoundsImplementation; /// @notice L2 Reward contract (in Proxy), which users interact with. address L2Reward; /// @notice The current implementation of L2 Reward contract. @@ -228,6 +236,31 @@ contract Utils is Script { l2AddressesConfig.L2LockingPositionPaused = l2LockingPositionPaused; } catch { } + try vm.parseJsonAddress(addressJson, ".L2MultiFeedAdapterWithoutRoundsPrimaryProd") returns ( + address l2MultiFeedAdapterWithoutRoundsPrimaryProd + ) { + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd = l2MultiFeedAdapterWithoutRoundsPrimaryProd; + } catch { } + + try vm.parseJsonAddress(addressJson, ".L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation") returns ( + address l2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation + ) { + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation = + l2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation; + } catch { } + + try vm.parseJsonAddress(addressJson, ".L2PriceFeedUsdtWithoutRounds") returns ( + address l2PriceFeedUsdtWithoutRounds + ) { + l2AddressesConfig.L2PriceFeedUsdtWithoutRounds = l2PriceFeedUsdtWithoutRounds; + } catch { } + + try vm.parseJsonAddress(addressJson, ".L2PriceFeedUsdtWithoutRoundsImplementation") returns ( + address l2PriceFeedUsdtWithoutRoundsImplementation + ) { + l2AddressesConfig.L2PriceFeedUsdtWithoutRoundsImplementation = l2PriceFeedUsdtWithoutRoundsImplementation; + } catch { } + try vm.parseJsonAddress(addressJson, ".L2RewardImplementation") returns (address l2RewardImplementation) { l2AddressesConfig.L2RewardImplementation = l2RewardImplementation; } catch { } @@ -294,6 +327,18 @@ contract Utils is Script { vm.serializeAddress(json, "L2LockingPosition", cfg.L2LockingPosition); vm.serializeAddress(json, "L2LockingPositionImplementation", cfg.L2LockingPositionImplementation); vm.serializeAddress(json, "L2LockingPositionPaused", cfg.L2LockingPositionPaused); + vm.serializeAddress( + json, "L2MultiFeedAdapterWithoutRoundsPrimaryProd", cfg.L2MultiFeedAdapterWithoutRoundsPrimaryProd + ); + vm.serializeAddress( + json, + "L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation", + cfg.L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation + ); + vm.serializeAddress(json, "L2PriceFeedUsdtWithoutRounds", cfg.L2PriceFeedUsdtWithoutRounds); + vm.serializeAddress( + json, "L2PriceFeedUsdtWithoutRoundsImplementation", cfg.L2PriceFeedUsdtWithoutRoundsImplementation + ); vm.serializeAddress(json, "L2Reward", cfg.L2Reward); vm.serializeAddress(json, "L2RewardImplementation", cfg.L2RewardImplementation); vm.serializeAddress(json, "L2RewardPaused", cfg.L2RewardPaused); diff --git a/src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol b/src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol new file mode 100644 index 00000000..e696331c --- /dev/null +++ b/src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { MultiFeedAdapterWithoutRounds } from + "@redstone-finance/on-chain-relayer/contracts/price-feeds/without-rounds/MultiFeedAdapterWithoutRounds.sol"; + +/// @title L2MultiFeedAdapterWithoutRoundsPrimaryProd - L2MultiFeedAdapterWithoutRoundsPrimaryProd contract +/// @notice This contract represents MultiFeedAdapterWithoutRounds contract for RedStone primary production environment. +/// It is used to manage multiple price feeds without rounds. This adapter contract allows updating any set of +/// data feeds, with each update being made independently. +contract L2MultiFeedAdapterWithoutRoundsPrimaryProd is + Initializable, + Ownable2StepUpgradeable, + UUPSUpgradeable, + MultiFeedAdapterWithoutRounds +{ + /// @notice The address of the Dedicated Message Sender (Gelato). + address internal constant DEDICATED_MESSAGE_SENDER_ADDRESS = 0x57D2460f4f401F1a675A2DC282344F926797e8e7; + + /// @notice Disabling initializers on implementation contract to prevent misuse. + constructor() { + _disableInitializers(); + } + + /// @notice Setting global params. + function initialize() public initializer { + __Ownable2Step_init(); + __Ownable_init(msg.sender); + } + + /// @notice Ensures that only the owner can authorize a contract upgrade. It reverts if called by any address other + /// than the contract owner. + /// @param _newImplementation The address of the new contract implementation to which the proxy will be upgraded. + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner { } + + /// @notice This function returns the number of unique signers required to update the data feeds. + /// @return The number of unique signers required to update the data feeds. + function getUniqueSignersThreshold() public view virtual override returns (uint8) { + return 2; + } + + /// @notice This function returns the index of the signer in the list of authorised signers. + /// @param signerAddress The address of the signer. + /// @return The index of the signer in the list of authorised signers. + function getAuthorisedSignerIndex(address signerAddress) public view virtual override returns (uint8) { + if (signerAddress == 0x8BB8F32Df04c8b654987DAaeD53D6B6091e3B774) return 0; + else if (signerAddress == 0xdEB22f54738d54976C4c0fe5ce6d408E40d88499) return 1; + else if (signerAddress == 0x51Ce04Be4b3E32572C4Ec9135221d0691Ba7d202) return 2; + else if (signerAddress == 0xDD682daEC5A90dD295d14DA4b0bec9281017b5bE) return 3; + else if (signerAddress == 0x9c5AE89C4Af6aA32cE58588DBaF90d18a855B6de) return 4; + else revert SignerNotAuthorised(signerAddress); + } + + /// @notice This function validates the block timestamp. + /// @param lastBlockTimestamp The timestamp of the last block. + /// @return A boolean value indicating whether the block timestamp is valid for price feed to be updated. + function _validateBlockTimestamp(uint256 lastBlockTimestamp) internal view virtual override returns (bool) { + if (msg.sender == owner() || msg.sender == DEDICATED_MESSAGE_SENDER_ADDRESS) { + // For whitelisted addresses we only require a newer block + return block.timestamp > lastBlockTimestamp; + } else { + // For non-whitelisted addresses we require some time to pass after the latest update + return block.timestamp > lastBlockTimestamp + 40 seconds; + } + } +} diff --git a/src/L2/L2PriceFeedLskWithoutRounds.sol b/src/L2/L2PriceFeedLskWithoutRounds.sol new file mode 100644 index 00000000..276bd514 --- /dev/null +++ b/src/L2/L2PriceFeedLskWithoutRounds.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { PriceFeedWithoutRoundsForMultiFeedAdapter } from + "@redstone-finance/on-chain-relayer/contracts/price-feeds/without-rounds/PriceFeedWithoutRoundsForMultiFeedAdapter.sol"; +import { IRedstoneAdapter } from "@redstone-finance/on-chain-relayer/contracts/core/IRedstoneAdapter.sol"; + +contract L2PriceFeedLskWithoutRounds is PriceFeedWithoutRoundsForMultiFeedAdapter { + function getDataFeedId() public view virtual override returns (bytes32) { + return bytes32("LSK"); + } + + function getPriceFeedAdapter() public view virtual override returns (IRedstoneAdapter) { + return IRedstoneAdapter(0xb5192ebA1DE69DA66A6C05Ba01C2514381a38c04); + } +} diff --git a/src/L2/L2PriceFeedUsdtWithoutRounds.sol b/src/L2/L2PriceFeedUsdtWithoutRounds.sol new file mode 100644 index 00000000..406d7f19 --- /dev/null +++ b/src/L2/L2PriceFeedUsdtWithoutRounds.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { PriceFeedWithoutRoundsForMultiFeedAdapter } from + "@redstone-finance/on-chain-relayer/contracts/price-feeds/without-rounds/PriceFeedWithoutRoundsForMultiFeedAdapter.sol"; +import { IRedstoneAdapter } from "@redstone-finance/on-chain-relayer/contracts/core/IRedstoneAdapter.sol"; + +/// @title L2PriceFeedUsdtWithoutRounds - L2PriceFeedUsdtWithoutRounds contract +/// @notice This contract represents PriceFeedWithoutRoundsForMultiFeedAdapter contract for USDT data feed. +contract L2PriceFeedUsdtWithoutRounds is + Initializable, + Ownable2StepUpgradeable, + UUPSUpgradeable, + PriceFeedWithoutRoundsForMultiFeedAdapter +{ + /// @notice Disabling initializers on implementation contract to prevent misuse. + constructor() { + _disableInitializers(); + } + + /// @notice Setting global params. + function initialize() public virtual override initializer { + super.initialize(); + __Ownable2Step_init(); + __Ownable_init(msg.sender); + } + + /// @notice Ensures that only the owner can authorize a contract upgrade. It reverts if called by any address other + /// than the contract owner. + /// @param _newImplementation The address of the new contract implementation to which the proxy will be upgraded. + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner { } + + /// @notice This function returns the data feed ID. + /// @return The data feed ID. + function getDataFeedId() public view virtual override returns (bytes32) { + return bytes32("USDT"); + } + + /// @notice This function returns the price feed adapter. + /// @return The price feed adapter. + function getPriceFeedAdapter() public view virtual override returns (IRedstoneAdapter) { + return IRedstoneAdapter(0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0); + } +} diff --git a/test/utils/Utils.t.sol b/test/utils/Utils.t.sol index a0c2111f..c121aece 100644 --- a/test/utils/Utils.t.sol +++ b/test/utils/Utils.t.sol @@ -48,6 +48,10 @@ contract UtilsTest is Test { L2LockingPosition: address(index++), L2LockingPositionImplementation: address(index++), L2LockingPositionPaused: address(index++), + L2MultiFeedAdapterWithoutRoundsPrimaryProd: address(index++), + L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation: address(index++), + L2PriceFeedUsdtWithoutRounds: address(index++), + L2PriceFeedUsdtWithoutRoundsImplementation: address(index++), L2Reward: address(index++), L2RewardImplementation: address(index++), L2RewardPaused: address(index++), From 723dcbf6fbdb5990b644b9a43efc1ff7850ad6b3 Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Wed, 16 Oct 2024 10:51:38 +0200 Subject: [PATCH 05/10] Implement Main Demo Adapter and PriceFeed for LSK --- ...10_deployMultiFeedAdaptersWithoutRounds.sh | 16 ++++ script/11_deployPriceFeedsWithoutRounds.sh | 16 ++++ ...ultiFeedAdapterWithoutRoundsMainDemo.s.sol | 84 +++++++++++++++++++ .../L2/L2PriceFeedLskWithoutRounds.s.sol | 66 ++++++++++++--- script/contracts/Utils.sol | 45 ++++++++++ ...2MultiFeedAdapterWithoutRoundsMainDemo.sol | 68 +++++++++++++++ src/L2/L2PriceFeedLskWithoutRounds.sol | 35 +++++++- test/utils/Utils.t.sol | 4 + 8 files changed, 322 insertions(+), 12 deletions(-) create mode 100644 script/contracts/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.s.sol create mode 100644 src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol diff --git a/script/10_deployMultiFeedAdaptersWithoutRounds.sh b/script/10_deployMultiFeedAdaptersWithoutRounds.sh index bb02590a..9075ec10 100755 --- a/script/10_deployMultiFeedAdaptersWithoutRounds.sh +++ b/script/10_deployMultiFeedAdaptersWithoutRounds.sh @@ -42,3 +42,19 @@ else fi fi echo "Done." + +echo "Deploying and if enabled verifying L2MultiFeedAdapterWithoutRoundsMainDemo smart contract..." +if [ -z "$CONTRACT_VERIFIER" ] +then + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.s.sol:L2MultiFeedAdapterWithoutRoundsMainDemoScript +else + if [ $CONTRACT_VERIFIER = "blockscout" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.s.sol:L2MultiFeedAdapterWithoutRoundsMainDemoScript + fi + if [ $CONTRACT_VERIFIER = "etherscan" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.s.sol:L2MultiFeedAdapterWithoutRoundsMainDemoScript + fi +fi +echo "Done." diff --git a/script/11_deployPriceFeedsWithoutRounds.sh b/script/11_deployPriceFeedsWithoutRounds.sh index 3ec8f841..4b549ffb 100755 --- a/script/11_deployPriceFeedsWithoutRounds.sh +++ b/script/11_deployPriceFeedsWithoutRounds.sh @@ -42,3 +42,19 @@ else fi fi echo "Done." + +echo "Deploying and if enabled verifying L2PriceFeedLskWithoutRounds smart contract..." +if [ -z "$CONTRACT_VERIFIER" ] +then + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol:L2PriceFeedLskWithoutRoundsScript +else + if [ $CONTRACT_VERIFIER = "blockscout" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol:L2PriceFeedLskWithoutRoundsScript + fi + if [ $CONTRACT_VERIFIER = "etherscan" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol:L2PriceFeedLskWithoutRoundsScript + fi +fi +echo "Done." diff --git a/script/contracts/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.s.sol b/script/contracts/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.s.sol new file mode 100644 index 00000000..078c3b3e --- /dev/null +++ b/script/contracts/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.s.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Script, console2 } from "forge-std/Script.sol"; +import { L2MultiFeedAdapterWithoutRoundsMainDemo } from "src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol"; +import "script/contracts/Utils.sol"; + +/// @title L2MultiFeedAdapterWithoutRoundsMainDemoScript - L2MultiFeedAdapterWithoutRoundsMainDemo deployment +/// script +/// @notice This contract is used to deploy L2MultiFeedAdapterWithoutRoundsMainDemo contract. +contract L2MultiFeedAdapterWithoutRoundsMainDemoScript is Script { + /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. + Utils utils; + + function setUp() public { + utils = new Utils(); + } + + /// @notice This function deploys L2MultiFeedAdapterWithoutRoundsMainDemo contract. + function run() public { + // Deployer's private key. Owner of the L2MultiFeedAdapterWithoutRoundsMainDemo. PRIVATE_KEY is set in .env + // file. + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + console2.log("Deploying L2MultiFeedAdapterWithoutRoundsMainDemo contract..."); + + // owner Address, the ownership of L2MultiFeedAdapterWithoutRoundsMainDemo proxy contract is transferred to + // after deployment + address ownerAddress = vm.envAddress("L2_ADAPTER_PRICEFEED_OWNER_ADDRESS"); + assert(ownerAddress != address(0)); + console2.log( + "L2 MultiFeed Adapter Without Rounds MainDemo contract owner address: %s (after ownership will be accepted)", + ownerAddress + ); + + // deploy L2MultiFeedAdapterWithoutRoundsMainDemo implementation contract + vm.startBroadcast(deployerPrivateKey); + L2MultiFeedAdapterWithoutRoundsMainDemo l2AdapterImplementation = new L2MultiFeedAdapterWithoutRoundsMainDemo(); + vm.stopBroadcast(); + + assert(address(l2AdapterImplementation) != address(0)); + + // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + assert( + l2AdapterImplementation.proxiableUUID() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) + ); + + // deploy L2MultiFeedAdapterWithoutRoundsMainDemo proxy contract and at the same time initialize the proxy + // contract (calls the initialize function in L2MultiFeedAdapterWithoutRoundsMainDemo) + vm.startBroadcast(deployerPrivateKey); + ERC1967Proxy l2AdapterProxy = new ERC1967Proxy( + address(l2AdapterImplementation), abi.encodeWithSelector(l2AdapterImplementation.initialize.selector) + ); + vm.stopBroadcast(); + assert(address(l2AdapterProxy) != address(0)); + + // wrap in ABI to support easier calls + L2MultiFeedAdapterWithoutRoundsMainDemo l2Adapter = + L2MultiFeedAdapterWithoutRoundsMainDemo(address(l2AdapterProxy)); + assert(l2Adapter.getUniqueSignersThreshold() == 1); + assert(l2Adapter.getAuthorisedSignerIndex(0x0C39486f770B26F5527BBBf942726537986Cd7eb) == 0); + + // transfer ownership of L2MultiFeedAdapterWithoutRoundsMainDemo proxy; because of using + // Ownable2StepUpgradeable contract, new owner has to accept ownership + vm.startBroadcast(deployerPrivateKey); + l2Adapter.transferOwnership(ownerAddress); + vm.stopBroadcast(); + assert(l2Adapter.owner() == vm.addr(deployerPrivateKey)); // ownership is not yet accepted + + console2.log("L2 MultiFeed Adapter Without Rounds MainDemo contract successfully deployed!"); + console2.log("L2 MultiFeed Adapter (Implementation) address: %s", address(l2AdapterImplementation)); + console2.log("L2 MultiFeed Adapter (Proxy) address: %s", address(l2Adapter)); + console2.log( + "Owner of L2 MultiFeed Adapter (Proxy) address: %s (after ownership will be accepted)", ownerAddress + ); + + // write L2MultiFeedAdapterWithoutRoundsMainDemo address to l2addresses.json + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemoImplementation = address(l2AdapterImplementation); + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo = address(l2Adapter); + utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); + } +} diff --git a/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol b/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol index 0bccf8cc..3431c349 100644 --- a/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol +++ b/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.23; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { Script, console2 } from "forge-std/Script.sol"; import { L2PriceFeedLskWithoutRounds } from "src/L2/L2PriceFeedLskWithoutRounds.sol"; import "script/contracts/Utils.sol"; -/// @title L2PriceFeedLskWithoutRoundsScript - L2PriceFeedLskWithoutRounds deployment script +/// @title L2PriceFeedLskWithoutRoundsScript - L2PriceFeedLskWithoutRounds deployment +/// script /// @notice This contract is used to deploy L2PriceFeedLskWithoutRounds contract. contract L2PriceFeedLskWithoutRoundsScript is Script { /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. @@ -17,24 +19,68 @@ contract L2PriceFeedLskWithoutRoundsScript is Script { /// @notice This function deploys L2PriceFeedLskWithoutRounds contract. function run() public { - // Deployer's private key. Owner of the L2PriceFeedLskWithoutRounds. PRIVATE_KEY is set in .env file. + // Deployer's private key. Owner of the L2PriceFeedLskWithoutRounds. PRIVATE_KEY is set in .env + // file. uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); console2.log("Deploying L2PriceFeedLskWithoutRounds contract..."); - // deploy L2PriceFeedLskWithoutRounds contract + // owner Address, the ownership of L2PriceFeedLskWithoutRounds proxy contract is transferred to + // after deployment + address ownerAddress = vm.envAddress("L2_ADAPTER_PRICEFEED_OWNER_ADDRESS"); + assert(ownerAddress != address(0)); + console2.log( + "L2 PriceFeed LSK Without Rounds contract owner address: %s (after ownership will be accepted)", + ownerAddress + ); + + // deploy L2PriceFeedLskWithoutRounds implementation contract vm.startBroadcast(deployerPrivateKey); - L2PriceFeedLskWithoutRounds l2PriceFeedLskWithoutRounds = new L2PriceFeedLskWithoutRounds(); + L2PriceFeedLskWithoutRounds l2PriceFeedImplementation = new L2PriceFeedLskWithoutRounds(); vm.stopBroadcast(); - assert(address(l2PriceFeedLskWithoutRounds) != address(0)); - //assert(l2PriceFeedLskWithoutRounds.l2LiskTokenAddress() == l2AddressesConfig.L2LiskToken); + assert(address(l2PriceFeedImplementation) != address(0)); + + // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + assert( + l2PriceFeedImplementation.proxiableUUID() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) + ); + + // deploy L2PriceFeedLskWithoutRounds proxy contract and at the same time initialize the proxy + // contract (calls the initialize function in L2PriceFeedLskWithoutRounds) + vm.startBroadcast(deployerPrivateKey); + ERC1967Proxy l2PriceFeedProxy = new ERC1967Proxy( + address(l2PriceFeedImplementation), abi.encodeWithSelector(l2PriceFeedImplementation.initialize.selector) + ); + vm.stopBroadcast(); + assert(address(l2PriceFeedProxy) != address(0)); + + // wrap in ABI to support easier calls + L2PriceFeedLskWithoutRounds l2PriceFeed = L2PriceFeedLskWithoutRounds(address(l2PriceFeedProxy)); + assert(l2PriceFeed.decimals() == 8); + assert(keccak256(bytes(l2PriceFeed.description())) == keccak256(bytes("Redstone Price Feed"))); + assert(l2PriceFeed.getDataFeedId() == bytes32("LSK")); + assert(address(l2PriceFeed.getPriceFeedAdapter()) == address(0x19664179Ad4823C6A51035a63C9032ed27ccA441)); + + // transfer ownership of L2PriceFeedLskWithoutRounds proxy; because of using + // Ownable2StepUpgradeable contract, new owner has to accept ownership + vm.startBroadcast(deployerPrivateKey); + l2PriceFeed.transferOwnership(ownerAddress); + vm.stopBroadcast(); + assert(l2PriceFeed.owner() == vm.addr(deployerPrivateKey)); // ownership is not yet accepted - console2.log("L2PriceFeedLskWithoutRounds successfully deployed!"); - console2.log("L2PriceFeedLskWithoutRounds address: %s", address(l2PriceFeedLskWithoutRounds)); + console2.log("L2 PriceFeed LSK Without Rounds contract successfully deployed!"); + console2.log("L2 PriceFeed LSK Without Rounds (Implementation) address: %s", address(l2PriceFeedImplementation)); + console2.log("L2 PriceFeed LSK Without Rounds (Proxy) address: %s", address(l2PriceFeed)); + console2.log( + "Owner of L2 PriceFeed LSK Without Rounds (Proxy) address: %s (after ownership will be accepted)", + ownerAddress + ); // write L2PriceFeedLskWithoutRounds address to l2addresses.json - //l2AddressesConfig.L2PriceFeedLskWithoutRounds = address(l2PriceFeedLskWithoutRounds); - //utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); + l2AddressesConfig.L2PriceFeedLskWithoutRoundsImplementation = address(l2PriceFeedImplementation); + l2AddressesConfig.L2PriceFeedLskWithoutRounds = address(l2PriceFeed); + utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); } } diff --git a/script/contracts/Utils.sol b/script/contracts/Utils.sol index 59242b86..99e173aa 100644 --- a/script/contracts/Utils.sol +++ b/script/contracts/Utils.sol @@ -43,10 +43,18 @@ contract Utils is Script { address L2LockingPositionImplementation; /// @notice L2 LockingPositionPaused address. address L2LockingPositionPaused; + /// @notice L2 MultiFeedAdapterWithoutRoundsMainDemo address. + address L2MultiFeedAdapterWithoutRoundsMainDemo; + /// @notice The current implementation of L2 MultiFeedAdapterWithoutRoundsMainDemo Contract. + address L2MultiFeedAdapterWithoutRoundsMainDemoImplementation; /// @notice L2 MultiFeedAdapterWithoutRoundsPrimaryProd address. address L2MultiFeedAdapterWithoutRoundsPrimaryProd; /// @notice The current implementation of L2 MultiFeedAdapterWithoutRoundsPrimaryProd Contract. address L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation; + /// @notice L2 PriceFeedLskWithoutRounds address. + address L2PriceFeedLskWithoutRounds; + /// @notice The current implementation of L2 PriceFeedLskWithoutRounds Contract. + address L2PriceFeedLskWithoutRoundsImplementation; /// @notice L2 PriceFeedUsdtWithoutRounds address. address L2PriceFeedUsdtWithoutRounds; /// @notice The current implementation of L2 PriceFeedUsdtWithoutRounds Contract. @@ -236,6 +244,19 @@ contract Utils is Script { l2AddressesConfig.L2LockingPositionPaused = l2LockingPositionPaused; } catch { } + try vm.parseJsonAddress(addressJson, ".L2MultiFeedAdapterWithoutRoundsMainDemo") returns ( + address l2MultiFeedAdapterWithoutRoundsMainDemo + ) { + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo = l2MultiFeedAdapterWithoutRoundsMainDemo; + } catch { } + + try vm.parseJsonAddress(addressJson, ".L2MultiFeedAdapterWithoutRoundsMainDemoImplementation") returns ( + address l2MultiFeedAdapterWithoutRoundsMainDemoImplementation + ) { + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemoImplementation = + l2MultiFeedAdapterWithoutRoundsMainDemoImplementation; + } catch { } + try vm.parseJsonAddress(addressJson, ".L2MultiFeedAdapterWithoutRoundsPrimaryProd") returns ( address l2MultiFeedAdapterWithoutRoundsPrimaryProd ) { @@ -249,6 +270,18 @@ contract Utils is Script { l2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation; } catch { } + try vm.parseJsonAddress(addressJson, ".L2PriceFeedLskWithoutRounds") returns ( + address l2PriceFeedLskWithoutRounds + ) { + l2AddressesConfig.L2PriceFeedLskWithoutRounds = l2PriceFeedLskWithoutRounds; + } catch { } + + try vm.parseJsonAddress(addressJson, ".L2PriceFeedLskWithoutRoundsImplementation") returns ( + address l2PriceFeedLskWithoutRoundsImplementation + ) { + l2AddressesConfig.L2PriceFeedLskWithoutRoundsImplementation = l2PriceFeedLskWithoutRoundsImplementation; + } catch { } + try vm.parseJsonAddress(addressJson, ".L2PriceFeedUsdtWithoutRounds") returns ( address l2PriceFeedUsdtWithoutRounds ) { @@ -327,6 +360,14 @@ contract Utils is Script { vm.serializeAddress(json, "L2LockingPosition", cfg.L2LockingPosition); vm.serializeAddress(json, "L2LockingPositionImplementation", cfg.L2LockingPositionImplementation); vm.serializeAddress(json, "L2LockingPositionPaused", cfg.L2LockingPositionPaused); + vm.serializeAddress( + json, "L2MultiFeedAdapterWithoutRoundsMainDemo", cfg.L2MultiFeedAdapterWithoutRoundsMainDemo + ); + vm.serializeAddress( + json, + "L2MultiFeedAdapterWithoutRoundsMainDemoImplementation", + cfg.L2MultiFeedAdapterWithoutRoundsMainDemoImplementation + ); vm.serializeAddress( json, "L2MultiFeedAdapterWithoutRoundsPrimaryProd", cfg.L2MultiFeedAdapterWithoutRoundsPrimaryProd ); @@ -335,6 +376,10 @@ contract Utils is Script { "L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation", cfg.L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation ); + vm.serializeAddress(json, "L2PriceFeedLskWithoutRounds", cfg.L2PriceFeedLskWithoutRounds); + vm.serializeAddress( + json, "L2PriceFeedLskWithoutRoundsImplementation", cfg.L2PriceFeedLskWithoutRoundsImplementation + ); vm.serializeAddress(json, "L2PriceFeedUsdtWithoutRounds", cfg.L2PriceFeedUsdtWithoutRounds); vm.serializeAddress( json, "L2PriceFeedUsdtWithoutRoundsImplementation", cfg.L2PriceFeedUsdtWithoutRoundsImplementation diff --git a/src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol b/src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol new file mode 100644 index 00000000..b68eb5be --- /dev/null +++ b/src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { MultiFeedAdapterWithoutRounds } from + "@redstone-finance/on-chain-relayer/contracts/price-feeds/without-rounds/MultiFeedAdapterWithoutRounds.sol"; + +/// @title L2MultiFeedAdapterWithoutRoundsMainDemo - L2MultiFeedAdapterWithoutRoundsMainDemo contract +/// @notice This contract represents MultiFeedAdapterWithoutRounds contract for RedStone main demo production +/// environment. It is used to manage multiple price feeds without rounds. This adapter contract allows updating +/// any set of data feeds, with each update being made independently. +contract L2MultiFeedAdapterWithoutRoundsMainDemo is + Initializable, + Ownable2StepUpgradeable, + UUPSUpgradeable, + MultiFeedAdapterWithoutRounds +{ + /// @notice The address of the Dedicated Message Sender (Gelato). + address internal constant DEDICATED_MESSAGE_SENDER_ADDRESS = 0x57D2460f4f401F1a675A2DC282344F926797e8e7; + + /// @notice Disabling initializers on implementation contract to prevent misuse. + constructor() { + _disableInitializers(); + } + + /// @notice Setting global params. + function initialize() public initializer { + __Ownable2Step_init(); + __Ownable_init(msg.sender); + } + + /// @notice Ensures that only the owner can authorize a contract upgrade. It reverts if called by any address other + /// than the contract owner. + /// @param _newImplementation The address of the new contract implementation to which the proxy will be upgraded. + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner { } + + /// @notice This function returns the number of unique signers required to update the data feeds. + /// @return The number of unique signers required to update the data feeds. + function getUniqueSignersThreshold() public view virtual override returns (uint8) { + return 1; + } + + /// @notice This function returns the index of the signer in the list of authorised signers. + /// @param signerAddress The address of the signer. + /// @return The index of the signer in the list of authorised signers. + function getAuthorisedSignerIndex(address signerAddress) public view virtual override returns (uint8) { + if (signerAddress == 0x0C39486f770B26F5527BBBf942726537986Cd7eb) { + return 0; + } else { + revert SignerNotAuthorised(signerAddress); + } + } + + /// @notice This function validates the block timestamp. + /// @param lastBlockTimestamp The timestamp of the last block. + /// @return A boolean value indicating whether the block timestamp is valid for price feed to be updated. + function _validateBlockTimestamp(uint256 lastBlockTimestamp) internal view virtual override returns (bool) { + if (msg.sender == owner() || msg.sender == DEDICATED_MESSAGE_SENDER_ADDRESS) { + // For whitelisted addresses we only require a newer block + return block.timestamp > lastBlockTimestamp; + } else { + // For non-whitelisted addresses we require some time to pass after the latest update + return block.timestamp > lastBlockTimestamp + 40 seconds; + } + } +} diff --git a/src/L2/L2PriceFeedLskWithoutRounds.sol b/src/L2/L2PriceFeedLskWithoutRounds.sol index 276bd514..8f61d271 100644 --- a/src/L2/L2PriceFeedLskWithoutRounds.sol +++ b/src/L2/L2PriceFeedLskWithoutRounds.sol @@ -1,16 +1,47 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.23; +import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import { PriceFeedWithoutRoundsForMultiFeedAdapter } from "@redstone-finance/on-chain-relayer/contracts/price-feeds/without-rounds/PriceFeedWithoutRoundsForMultiFeedAdapter.sol"; import { IRedstoneAdapter } from "@redstone-finance/on-chain-relayer/contracts/core/IRedstoneAdapter.sol"; -contract L2PriceFeedLskWithoutRounds is PriceFeedWithoutRoundsForMultiFeedAdapter { +/// @title L2PriceFeedLskWithoutRounds - L2PriceFeedLskWithoutRounds contract +/// @notice This contract represents PriceFeedWithoutRoundsForMultiFeedAdapter contract for LSK data feed. +contract L2PriceFeedLskWithoutRounds is + Initializable, + Ownable2StepUpgradeable, + UUPSUpgradeable, + PriceFeedWithoutRoundsForMultiFeedAdapter +{ + /// @notice Disabling initializers on implementation contract to prevent misuse. + constructor() { + _disableInitializers(); + } + + /// @notice Setting global params. + function initialize() public virtual override initializer { + super.initialize(); + __Ownable2Step_init(); + __Ownable_init(msg.sender); + } + + /// @notice Ensures that only the owner can authorize a contract upgrade. It reverts if called by any address other + /// than the contract owner. + /// @param _newImplementation The address of the new contract implementation to which the proxy will be upgraded. + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner { } + + /// @notice This function returns the data feed ID. + /// @return The data feed ID. function getDataFeedId() public view virtual override returns (bytes32) { return bytes32("LSK"); } + /// @notice This function returns the price feed adapter. + /// @return The price feed adapter. function getPriceFeedAdapter() public view virtual override returns (IRedstoneAdapter) { - return IRedstoneAdapter(0xb5192ebA1DE69DA66A6C05Ba01C2514381a38c04); + return IRedstoneAdapter(0x19664179Ad4823C6A51035a63C9032ed27ccA441); } } diff --git a/test/utils/Utils.t.sol b/test/utils/Utils.t.sol index c121aece..92f371b6 100644 --- a/test/utils/Utils.t.sol +++ b/test/utils/Utils.t.sol @@ -48,8 +48,12 @@ contract UtilsTest is Test { L2LockingPosition: address(index++), L2LockingPositionImplementation: address(index++), L2LockingPositionPaused: address(index++), + L2MultiFeedAdapterWithoutRoundsMainDemo: address(index++), + L2MultiFeedAdapterWithoutRoundsMainDemoImplementation: address(index++), L2MultiFeedAdapterWithoutRoundsPrimaryProd: address(index++), L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation: address(index++), + L2PriceFeedLskWithoutRounds: address(index++), + L2PriceFeedLskWithoutRoundsImplementation: address(index++), L2PriceFeedUsdtWithoutRounds: address(index++), L2PriceFeedUsdtWithoutRoundsImplementation: address(index++), L2Reward: address(index++), From 02fabb50110abad08ac363a6436fc485086158be Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Fri, 18 Oct 2024 08:46:01 +0200 Subject: [PATCH 06/10] Add unit tests for Adapter and PriceFeed contracts --- ...ultiFeedAdapterWithoutRoundsMainDemo.t.sol | 133 +++++++++++++++++ ...iFeedAdapterWithoutRoundsPrimaryProd.t.sol | 141 ++++++++++++++++++ test/L2/L2PriceFeedLskWithoutRounds.t.sol | 134 +++++++++++++++++ test/L2/L2PriceFeedUsdtWithoutRounds.t.sol | 134 +++++++++++++++++ 4 files changed, 542 insertions(+) create mode 100644 test/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.t.sol create mode 100644 test/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.t.sol create mode 100644 test/L2/L2PriceFeedLskWithoutRounds.t.sol create mode 100644 test/L2/L2PriceFeedUsdtWithoutRounds.t.sol diff --git a/test/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.t.sol b/test/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.t.sol new file mode 100644 index 00000000..cfdc3927 --- /dev/null +++ b/test/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.t.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import { Test } from "forge-std/Test.sol"; +import { L2MultiFeedAdapterWithoutRoundsMainDemo } from "src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol"; + +contract L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock is L2MultiFeedAdapterWithoutRoundsMainDemo { + string public testVersion; + + function initializeV2(string memory _version) public reinitializer(2) { + testVersion = _version; + } + + function onlyV2() public pure returns (string memory) { + return "Hello from V2"; + } +} + +contract L2MultiFeedAdapterWithoutRoundsMainDemoTest is Test { + L2MultiFeedAdapterWithoutRoundsMainDemo public l2Adapter; + L2MultiFeedAdapterWithoutRoundsMainDemo public l2AdapterImplementation; + + function setUp() public { + // deploy L2MultiFeedAdapterWithoutRoundsMainDemo Implementation contract + l2AdapterImplementation = new L2MultiFeedAdapterWithoutRoundsMainDemo(); + + // deploy L2MultiFeedAdapterWithoutRoundsMainDemo contract via Proxy and initialize it at the same time + l2Adapter = L2MultiFeedAdapterWithoutRoundsMainDemo( + address( + new ERC1967Proxy( + address(l2AdapterImplementation), abi.encodeWithSelector(l2Adapter.initialize.selector) + ) + ) + ); + assertEq(l2Adapter.getUniqueSignersThreshold(), 1); + assertEq(l2Adapter.getAuthorisedSignerIndex(0x0C39486f770B26F5527BBBf942726537986Cd7eb), 0); + } + + function test_TransferOwnership() public { + address newOwner = vm.addr(1); + + l2Adapter.transferOwnership(newOwner); + assertEq(l2Adapter.owner(), address(this)); + + vm.prank(newOwner); + l2Adapter.acceptOwnership(); + assertEq(l2Adapter.owner(), newOwner); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByOwner(uint256 _addressSeed) public { + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + address newOwner = vm.addr(1); + + if (nobody == address(this)) { + return; + } + + // owner is this contract + assertEq(l2Adapter.owner(), address(this)); + + // address nobody is not the owner so it cannot call transferOwnership + vm.startPrank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2Adapter.transferOwnership(newOwner); + vm.stopPrank(); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByPendingOwner(uint256 _addressSeed) public { + address newOwner = vm.addr(1); + + l2Adapter.transferOwnership(newOwner); + assertEq(l2Adapter.owner(), address(this)); + + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == newOwner) { + return; + } + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2Adapter.acceptOwnership(); + } + + function testFuzz_UpgradeToAndCall_RevertWhenNotOwner(uint256 _addressSeed) public { + // deploy L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock implementation contract + L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock l2AdapterV2Implementation = + new L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock(); + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == address(this)) { + return; + } + + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2Adapter.upgradeToAndCall(address(l2AdapterV2Implementation), ""); + } + + function test_UpgradeToAndCall_SuccessUpgrade() public { + // deploy L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock implementation contract + L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock l2AdapterV2Implementation = + new L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock(); + + // upgrade contract, and also change some variables by reinitialize + l2Adapter.upgradeToAndCall( + address(l2AdapterV2Implementation), + abi.encodeWithSelector(l2AdapterV2Implementation.initializeV2.selector, "v2.0.0") + ); + + // wrap L2MultiFeedAdapterWithoutRoundsMainDemo proxy with new contract + L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock l2AdapterV2 = + L2MultiFeedAdapterWithoutRoundsMainDemoV2Mock(address(l2Adapter)); + + // signer threshold and signer index should remain the same + assertEq(l2AdapterV2.getUniqueSignersThreshold(), 1); + assertEq(l2AdapterV2.getAuthorisedSignerIndex(0x0C39486f770B26F5527BBBf942726537986Cd7eb), 0); + + // version of L2MultiFeedAdapterWithoutRoundsMainDemo set to v2.0.0 + assertEq(l2AdapterV2.testVersion(), "v2.0.0"); + + // new function introduced + assertEq(l2AdapterV2.onlyV2(), "Hello from V2"); + + // assure cannot re-reinitialize + vm.expectRevert(); + l2AdapterV2.initializeV2("v3.0.0"); + } +} diff --git a/test/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.t.sol b/test/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.t.sol new file mode 100644 index 00000000..1c0ffcaf --- /dev/null +++ b/test/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.t.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import { Test } from "forge-std/Test.sol"; +import { L2MultiFeedAdapterWithoutRoundsPrimaryProd } from "src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol"; + +contract L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock is L2MultiFeedAdapterWithoutRoundsPrimaryProd { + string public testVersion; + + function initializeV2(string memory _version) public reinitializer(2) { + testVersion = _version; + } + + function onlyV2() public pure returns (string memory) { + return "Hello from V2"; + } +} + +contract L2MultiFeedAdapterWithoutRoundsPrimaryProdTest is Test { + L2MultiFeedAdapterWithoutRoundsPrimaryProd public l2Adapter; + L2MultiFeedAdapterWithoutRoundsPrimaryProd public l2AdapterImplementation; + + function setUp() public { + // deploy L2MultiFeedAdapterWithoutRoundsPrimaryProd Implementation contract + l2AdapterImplementation = new L2MultiFeedAdapterWithoutRoundsPrimaryProd(); + + // deploy L2MultiFeedAdapterWithoutRoundsPrimaryProd contract via Proxy and initialize it at the same time + l2Adapter = L2MultiFeedAdapterWithoutRoundsPrimaryProd( + address( + new ERC1967Proxy( + address(l2AdapterImplementation), abi.encodeWithSelector(l2Adapter.initialize.selector) + ) + ) + ); + assertEq(l2Adapter.getUniqueSignersThreshold(), 2); + assertEq(l2Adapter.getAuthorisedSignerIndex(0x8BB8F32Df04c8b654987DAaeD53D6B6091e3B774), 0); + assertEq(l2Adapter.getAuthorisedSignerIndex(0xdEB22f54738d54976C4c0fe5ce6d408E40d88499), 1); + assertEq(l2Adapter.getAuthorisedSignerIndex(0x51Ce04Be4b3E32572C4Ec9135221d0691Ba7d202), 2); + assertEq(l2Adapter.getAuthorisedSignerIndex(0xDD682daEC5A90dD295d14DA4b0bec9281017b5bE), 3); + assertEq(l2Adapter.getAuthorisedSignerIndex(0x9c5AE89C4Af6aA32cE58588DBaF90d18a855B6de), 4); + } + + function test_TransferOwnership() public { + address newOwner = vm.addr(1); + + l2Adapter.transferOwnership(newOwner); + assertEq(l2Adapter.owner(), address(this)); + + vm.prank(newOwner); + l2Adapter.acceptOwnership(); + assertEq(l2Adapter.owner(), newOwner); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByOwner(uint256 _addressSeed) public { + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + address newOwner = vm.addr(1); + + if (nobody == address(this)) { + return; + } + + // owner is this contract + assertEq(l2Adapter.owner(), address(this)); + + // address nobody is not the owner so it cannot call transferOwnership + vm.startPrank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2Adapter.transferOwnership(newOwner); + vm.stopPrank(); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByPendingOwner(uint256 _addressSeed) public { + address newOwner = vm.addr(1); + + l2Adapter.transferOwnership(newOwner); + assertEq(l2Adapter.owner(), address(this)); + + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == newOwner) { + return; + } + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2Adapter.acceptOwnership(); + } + + function testFuzz_UpgradeToAndCall_RevertWhenNotOwner(uint256 _addressSeed) public { + // deploy L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock implementation contract + L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock l2AdapterV2Implementation = + new L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock(); + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == address(this)) { + return; + } + + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2Adapter.upgradeToAndCall(address(l2AdapterV2Implementation), ""); + } + + function test_UpgradeToAndCall_SuccessUpgrade() public { + // deploy L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock implementation contract + L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock l2AdapterV2Implementation = + new L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock(); + + // upgrade contract, and also change some variables by reinitialize + l2Adapter.upgradeToAndCall( + address(l2AdapterV2Implementation), + abi.encodeWithSelector(l2AdapterV2Implementation.initializeV2.selector, "v2.0.0") + ); + + // wrap L2MultiFeedAdapterWithoutRoundsPrimaryProd proxy with new contract + L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock l2AdapterV2 = + L2MultiFeedAdapterWithoutRoundsPrimaryProdV2Mock(address(l2Adapter)); + + // signer threshold and signer index should remain the same + assertEq(l2AdapterV2.getUniqueSignersThreshold(), 2); + assertEq(l2AdapterV2.getAuthorisedSignerIndex(0x8BB8F32Df04c8b654987DAaeD53D6B6091e3B774), 0); + assertEq(l2AdapterV2.getAuthorisedSignerIndex(0xdEB22f54738d54976C4c0fe5ce6d408E40d88499), 1); + assertEq(l2AdapterV2.getAuthorisedSignerIndex(0x51Ce04Be4b3E32572C4Ec9135221d0691Ba7d202), 2); + assertEq(l2AdapterV2.getAuthorisedSignerIndex(0xDD682daEC5A90dD295d14DA4b0bec9281017b5bE), 3); + assertEq(l2AdapterV2.getAuthorisedSignerIndex(0x9c5AE89C4Af6aA32cE58588DBaF90d18a855B6de), 4); + + // version of L2MultiFeedAdapterWithoutRoundsPrimaryProd set to v2.0.0 + assertEq(l2AdapterV2.testVersion(), "v2.0.0"); + + // new function introduced + assertEq(l2AdapterV2.onlyV2(), "Hello from V2"); + + // assure cannot re-reinitialize + vm.expectRevert(); + l2AdapterV2.initializeV2("v3.0.0"); + } +} diff --git a/test/L2/L2PriceFeedLskWithoutRounds.t.sol b/test/L2/L2PriceFeedLskWithoutRounds.t.sol new file mode 100644 index 00000000..b9d712cf --- /dev/null +++ b/test/L2/L2PriceFeedLskWithoutRounds.t.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import { Test } from "forge-std/Test.sol"; +import { L2PriceFeedLskWithoutRounds } from "src/L2/L2PriceFeedLskWithoutRounds.sol"; + +contract L2PriceFeedLskWithoutRoundsV2Mock is L2PriceFeedLskWithoutRounds { + string public testVersion; + + function initializeV2(string memory _version) public reinitializer(2) { + testVersion = _version; + } + + function onlyV2() public pure returns (string memory) { + return "Hello from V2"; + } +} + +contract L2PriceFeedLskWithoutRoundsTest is Test { + L2PriceFeedLskWithoutRounds public l2PriceFeed; + L2PriceFeedLskWithoutRounds public l2PriceFeedImplementation; + + function setUp() public { + // deploy L2PriceFeedLskWithoutRounds Implementation contract + l2PriceFeedImplementation = new L2PriceFeedLskWithoutRounds(); + + // deploy L2PriceFeedLskWithoutRounds contract via Proxy and initialize it at the same time + l2PriceFeed = L2PriceFeedLskWithoutRounds( + address( + new ERC1967Proxy( + address(l2PriceFeedImplementation), abi.encodeWithSelector(l2PriceFeed.initialize.selector) + ) + ) + ); + assertEq(l2PriceFeed.decimals(), 8); + assertEq(keccak256(bytes(l2PriceFeed.description())), keccak256(bytes("Redstone Price Feed"))); + assertEq(l2PriceFeed.getDataFeedId(), bytes32("LSK")); + assertEq(address(l2PriceFeed.getPriceFeedAdapter()), address(0x19664179Ad4823C6A51035a63C9032ed27ccA441)); + } + + function test_TransferOwnership() public { + address newOwner = vm.addr(1); + + l2PriceFeed.transferOwnership(newOwner); + assertEq(l2PriceFeed.owner(), address(this)); + + vm.prank(newOwner); + l2PriceFeed.acceptOwnership(); + assertEq(l2PriceFeed.owner(), newOwner); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByOwner(uint256 _addressSeed) public { + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + address newOwner = vm.addr(1); + + if (nobody == address(this)) { + return; + } + + // owner is this contract + assertEq(l2PriceFeed.owner(), address(this)); + + // address nobody is not the owner so it cannot call transferOwnership + vm.startPrank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeed.transferOwnership(newOwner); + vm.stopPrank(); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByPendingOwner(uint256 _addressSeed) public { + address newOwner = vm.addr(1); + + l2PriceFeed.transferOwnership(newOwner); + assertEq(l2PriceFeed.owner(), address(this)); + + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == newOwner) { + return; + } + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeed.acceptOwnership(); + } + + function testFuzz_UpgradeToAndCall_RevertWhenNotOwner(uint256 _addressSeed) public { + // deploy L2PriceFeedLskWithoutRoundsV2Mock implementation contract + L2PriceFeedLskWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedLskWithoutRoundsV2Mock(); + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == address(this)) { + return; + } + + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeed.upgradeToAndCall(address(l2PriceFeedV2Implementation), ""); + } + + function test_UpgradeToAndCall_SuccessUpgrade() public { + // deploy L2PriceFeedLskWithoutRoundsV2Mock implementation contract + L2PriceFeedLskWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedLskWithoutRoundsV2Mock(); + + // upgrade contract, and also change some variables by reinitialize + l2PriceFeed.upgradeToAndCall( + address(l2PriceFeedV2Implementation), + abi.encodeWithSelector(l2PriceFeedV2Implementation.initializeV2.selector, "v2.0.0") + ); + + // wrap L2PriceFeedLskWithoutRounds proxy with new contract + L2PriceFeedLskWithoutRoundsV2Mock l2PriceFeedV2 = L2PriceFeedLskWithoutRoundsV2Mock(address(l2PriceFeed)); + + // check if the upgrade was successful and the variables are the same + assertEq(l2PriceFeedV2.decimals(), 8); + assertEq(keccak256(bytes(l2PriceFeedV2.description())), keccak256(bytes("Redstone Price Feed"))); + assertEq(l2PriceFeedV2.getDataFeedId(), bytes32("LSK")); + assertEq(address(l2PriceFeedV2.getPriceFeedAdapter()), address(0x19664179Ad4823C6A51035a63C9032ed27ccA441)); + + // version of L2PriceFeedLskWithoutRounds set to v2.0.0 + assertEq(l2PriceFeedV2.testVersion(), "v2.0.0"); + + // new function introduced + assertEq(l2PriceFeedV2.onlyV2(), "Hello from V2"); + + // assure cannot re-reinitialize + vm.expectRevert(); + l2PriceFeedV2.initializeV2("v3.0.0"); + } +} diff --git a/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol b/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol new file mode 100644 index 00000000..322043e1 --- /dev/null +++ b/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import { Test } from "forge-std/Test.sol"; +import { L2PriceFeedUsdtWithoutRounds } from "src/L2/L2PriceFeedUsdtWithoutRounds.sol"; + +contract L2PriceFeedUsdtWithoutRoundsV2Mock is L2PriceFeedUsdtWithoutRounds { + string public testVersion; + + function initializeV2(string memory _version) public reinitializer(2) { + testVersion = _version; + } + + function onlyV2() public pure returns (string memory) { + return "Hello from V2"; + } +} + +contract L2PriceFeedUsdtWithoutRoundsTest is Test { + L2PriceFeedUsdtWithoutRounds public l2PriceFeed; + L2PriceFeedUsdtWithoutRounds public l2PriceFeedImplementation; + + function setUp() public { + // deploy L2PriceFeedUsdtWithoutRounds Implementation contract + l2PriceFeedImplementation = new L2PriceFeedUsdtWithoutRounds(); + + // deploy L2PriceFeedUsdtWithoutRounds contract via Proxy and initialize it at the same time + l2PriceFeed = L2PriceFeedUsdtWithoutRounds( + address( + new ERC1967Proxy( + address(l2PriceFeedImplementation), abi.encodeWithSelector(l2PriceFeed.initialize.selector) + ) + ) + ); + assertEq(l2PriceFeed.decimals(), 8); + assertEq(keccak256(bytes(l2PriceFeed.description())), keccak256(bytes("Redstone Price Feed"))); + assertEq(l2PriceFeed.getDataFeedId(), bytes32("USDT")); + assertEq(address(l2PriceFeed.getPriceFeedAdapter()), address(0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0)); + } + + function test_TransferOwnership() public { + address newOwner = vm.addr(1); + + l2PriceFeed.transferOwnership(newOwner); + assertEq(l2PriceFeed.owner(), address(this)); + + vm.prank(newOwner); + l2PriceFeed.acceptOwnership(); + assertEq(l2PriceFeed.owner(), newOwner); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByOwner(uint256 _addressSeed) public { + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + address newOwner = vm.addr(1); + + if (nobody == address(this)) { + return; + } + + // owner is this contract + assertEq(l2PriceFeed.owner(), address(this)); + + // address nobody is not the owner so it cannot call transferOwnership + vm.startPrank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeed.transferOwnership(newOwner); + vm.stopPrank(); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByPendingOwner(uint256 _addressSeed) public { + address newOwner = vm.addr(1); + + l2PriceFeed.transferOwnership(newOwner); + assertEq(l2PriceFeed.owner(), address(this)); + + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == newOwner) { + return; + } + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeed.acceptOwnership(); + } + + function testFuzz_UpgradeToAndCall_RevertWhenNotOwner(uint256 _addressSeed) public { + // deploy L2PriceFeedUsdtWithoutRoundsV2Mock implementation contract + L2PriceFeedUsdtWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedUsdtWithoutRoundsV2Mock(); + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == address(this)) { + return; + } + + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeed.upgradeToAndCall(address(l2PriceFeedV2Implementation), ""); + } + + function test_UpgradeToAndCall_SuccessUpgrade() public { + // deploy L2PriceFeedUsdtWithoutRoundsV2Mock implementation contract + L2PriceFeedUsdtWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedUsdtWithoutRoundsV2Mock(); + + // upgrade contract, and also change some variables by reinitialize + l2PriceFeed.upgradeToAndCall( + address(l2PriceFeedV2Implementation), + abi.encodeWithSelector(l2PriceFeedV2Implementation.initializeV2.selector, "v2.0.0") + ); + + // wrap L2PriceFeedUsdtWithoutRounds proxy with new contract + L2PriceFeedUsdtWithoutRoundsV2Mock l2PriceFeedV2 = L2PriceFeedUsdtWithoutRoundsV2Mock(address(l2PriceFeed)); + + // check if the upgrade was successful and the variables are the same + assertEq(l2PriceFeedV2.decimals(), 8); + assertEq(keccak256(bytes(l2PriceFeedV2.description())), keccak256(bytes("Redstone Price Feed"))); + assertEq(l2PriceFeedV2.getDataFeedId(), bytes32("USDT")); + assertEq(address(l2PriceFeedV2.getPriceFeedAdapter()), address(0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0)); + + // version of L2PriceFeedUsdtWithoutRounds set to v2.0.0 + assertEq(l2PriceFeedV2.testVersion(), "v2.0.0"); + + // new function introduced + assertEq(l2PriceFeedV2.onlyV2(), "Hello from V2"); + + // assure cannot re-reinitialize + vm.expectRevert(); + l2PriceFeedV2.initializeV2("v3.0.0"); + } +} From 178694ad0bbc6402d8888ee0937389784164ab5f Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Fri, 18 Oct 2024 14:02:56 +0200 Subject: [PATCH 07/10] Added logic to set PriceFeedAdapter address --- .../L2/L2PriceFeedLskWithoutRounds.s.sol | 16 ++++++++++-- .../L2/L2PriceFeedUsdtWithoutRounds.s.sol | 18 +++++++++++-- src/L2/L2PriceFeedLskWithoutRounds.sol | 14 +++++++++- src/L2/L2PriceFeedUsdtWithoutRounds.sol | 14 +++++++++- test/L2/L2PriceFeedLskWithoutRounds.t.sol | 26 +++++++++++++++++-- test/L2/L2PriceFeedUsdtWithoutRounds.t.sol | 26 +++++++++++++++++-- 6 files changed, 104 insertions(+), 10 deletions(-) diff --git a/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol b/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol index 3431c349..46d273e0 100644 --- a/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol +++ b/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol @@ -34,6 +34,14 @@ contract L2PriceFeedLskWithoutRoundsScript is Script { ownerAddress ); + // get L2MultiFeedAdapterWithoutRoundsMainDemo contract address + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); + assert(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo != address(0)); + console2.log( + "L2 MultiFeed Adapter Without Rounds MainDemo address: %s", + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo + ); + // deploy L2PriceFeedLskWithoutRounds implementation contract vm.startBroadcast(deployerPrivateKey); L2PriceFeedLskWithoutRounds l2PriceFeedImplementation = new L2PriceFeedLskWithoutRounds(); @@ -60,7 +68,12 @@ contract L2PriceFeedLskWithoutRoundsScript is Script { assert(l2PriceFeed.decimals() == 8); assert(keccak256(bytes(l2PriceFeed.description())) == keccak256(bytes("Redstone Price Feed"))); assert(l2PriceFeed.getDataFeedId() == bytes32("LSK")); - assert(address(l2PriceFeed.getPriceFeedAdapter()) == address(0x19664179Ad4823C6A51035a63C9032ed27ccA441)); + + // set L2MultiFeedAdapterWithoutRoundsMainDemo contract address as a PriceFeedAdapter + vm.startBroadcast(deployerPrivateKey); + l2PriceFeed.setPriceFeedAdapter(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo); + vm.stopBroadcast(); + assert(address(l2PriceFeed.getPriceFeedAdapter()) == l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo); // transfer ownership of L2PriceFeedLskWithoutRounds proxy; because of using // Ownable2StepUpgradeable contract, new owner has to accept ownership @@ -78,7 +91,6 @@ contract L2PriceFeedLskWithoutRoundsScript is Script { ); // write L2PriceFeedLskWithoutRounds address to l2addresses.json - Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); l2AddressesConfig.L2PriceFeedLskWithoutRoundsImplementation = address(l2PriceFeedImplementation); l2AddressesConfig.L2PriceFeedLskWithoutRounds = address(l2PriceFeed); utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); diff --git a/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol b/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol index e7f3ece4..ad4f9edb 100644 --- a/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol +++ b/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol @@ -34,6 +34,14 @@ contract L2PriceFeedUsdtWithoutRoundsScript is Script { ownerAddress ); + // get L2MultiFeedAdapterWithoutRoundsPrimaryProd contract address + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); + assert(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd != address(0)); + console2.log( + "L2 MultiFeed Adapter Without Rounds PrimaryProd address: %s", + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd + ); + // deploy L2PriceFeedUsdtWithoutRounds implementation contract vm.startBroadcast(deployerPrivateKey); L2PriceFeedUsdtWithoutRounds l2PriceFeedImplementation = new L2PriceFeedUsdtWithoutRounds(); @@ -60,7 +68,14 @@ contract L2PriceFeedUsdtWithoutRoundsScript is Script { assert(l2PriceFeed.decimals() == 8); assert(keccak256(bytes(l2PriceFeed.description())) == keccak256(bytes("Redstone Price Feed"))); assert(l2PriceFeed.getDataFeedId() == bytes32("USDT")); - assert(address(l2PriceFeed.getPriceFeedAdapter()) == address(0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0)); + + // set L2MultiFeedAdapterWithoutRoundsPrimaryProd contract address as a PriceFeedAdapter + vm.startBroadcast(deployerPrivateKey); + l2PriceFeed.setPriceFeedAdapter(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd); + vm.stopBroadcast(); + assert( + address(l2PriceFeed.getPriceFeedAdapter()) == l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd + ); // transfer ownership of L2PriceFeedUsdtWithoutRounds proxy; because of using // Ownable2StepUpgradeable contract, new owner has to accept ownership @@ -80,7 +95,6 @@ contract L2PriceFeedUsdtWithoutRoundsScript is Script { ); // write L2PriceFeedUsdtWithoutRounds address to l2addresses.json - Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); l2AddressesConfig.L2PriceFeedUsdtWithoutRoundsImplementation = address(l2PriceFeedImplementation); l2AddressesConfig.L2PriceFeedUsdtWithoutRounds = address(l2PriceFeed); utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); diff --git a/src/L2/L2PriceFeedLskWithoutRounds.sol b/src/L2/L2PriceFeedLskWithoutRounds.sol index 8f61d271..7282d50f 100644 --- a/src/L2/L2PriceFeedLskWithoutRounds.sol +++ b/src/L2/L2PriceFeedLskWithoutRounds.sol @@ -16,6 +16,9 @@ contract L2PriceFeedLskWithoutRounds is UUPSUpgradeable, PriceFeedWithoutRoundsForMultiFeedAdapter { + /// @notice The address of the MultiFeedAdapter contract. + address internal priceFeedAdapter; + /// @notice Disabling initializers on implementation contract to prevent misuse. constructor() { _disableInitializers(); @@ -42,6 +45,15 @@ contract L2PriceFeedLskWithoutRounds is /// @notice This function returns the price feed adapter. /// @return The price feed adapter. function getPriceFeedAdapter() public view virtual override returns (IRedstoneAdapter) { - return IRedstoneAdapter(0x19664179Ad4823C6A51035a63C9032ed27ccA441); + return IRedstoneAdapter(priceFeedAdapter); + } + + /// @notice This function sets the address of the MultiFeedAdapter contract. + /// @param _adapter The address of the MultiFeedAdapter contract. + function setPriceFeedAdapter(address _adapter) public virtual onlyOwner { + require(priceFeedAdapter == address(0), "L2PriceFeedLskWithoutRounds: priceFeedAdapter is already initialized"); + require(_adapter != address(0), "L2PriceFeedLskWithoutRounds: priceFeedAdapter address can not be zero"); + + priceFeedAdapter = _adapter; } } diff --git a/src/L2/L2PriceFeedUsdtWithoutRounds.sol b/src/L2/L2PriceFeedUsdtWithoutRounds.sol index 406d7f19..2c71ae67 100644 --- a/src/L2/L2PriceFeedUsdtWithoutRounds.sol +++ b/src/L2/L2PriceFeedUsdtWithoutRounds.sol @@ -16,6 +16,9 @@ contract L2PriceFeedUsdtWithoutRounds is UUPSUpgradeable, PriceFeedWithoutRoundsForMultiFeedAdapter { + /// @notice The address of the MultiFeedAdapter contract. + address internal priceFeedAdapter; + /// @notice Disabling initializers on implementation contract to prevent misuse. constructor() { _disableInitializers(); @@ -42,6 +45,15 @@ contract L2PriceFeedUsdtWithoutRounds is /// @notice This function returns the price feed adapter. /// @return The price feed adapter. function getPriceFeedAdapter() public view virtual override returns (IRedstoneAdapter) { - return IRedstoneAdapter(0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0); + return IRedstoneAdapter(priceFeedAdapter); + } + + /// @notice This function sets the address of the MultiFeedAdapter contract. + /// @param _adapter The address of the MultiFeedAdapter contract. + function setPriceFeedAdapter(address _adapter) public virtual onlyOwner { + require(priceFeedAdapter == address(0), "L2PriceFeedLskWithoutRounds: priceFeedAdapter is already initialized"); + require(_adapter != address(0), "L2PriceFeedLskWithoutRounds: priceFeedAdapter address can not be zero"); + + priceFeedAdapter = _adapter; } } diff --git a/test/L2/L2PriceFeedLskWithoutRounds.t.sol b/test/L2/L2PriceFeedLskWithoutRounds.t.sol index b9d712cf..7cfa76ee 100644 --- a/test/L2/L2PriceFeedLskWithoutRounds.t.sol +++ b/test/L2/L2PriceFeedLskWithoutRounds.t.sol @@ -22,6 +22,8 @@ contract L2PriceFeedLskWithoutRoundsTest is Test { L2PriceFeedLskWithoutRounds public l2PriceFeed; L2PriceFeedLskWithoutRounds public l2PriceFeedImplementation; + address public priceFeedAdapter = 0x19664179Ad4823C6A51035a63C9032ed27ccA441; + function setUp() public { // deploy L2PriceFeedLskWithoutRounds Implementation contract l2PriceFeedImplementation = new L2PriceFeedLskWithoutRounds(); @@ -37,7 +39,27 @@ contract L2PriceFeedLskWithoutRoundsTest is Test { assertEq(l2PriceFeed.decimals(), 8); assertEq(keccak256(bytes(l2PriceFeed.description())), keccak256(bytes("Redstone Price Feed"))); assertEq(l2PriceFeed.getDataFeedId(), bytes32("LSK")); - assertEq(address(l2PriceFeed.getPriceFeedAdapter()), address(0x19664179Ad4823C6A51035a63C9032ed27ccA441)); + assertEq(address(l2PriceFeed.getPriceFeedAdapter()), address(0)); + + // set PriceFeedAdapter contract address + l2PriceFeed.setPriceFeedAdapter(priceFeedAdapter); + assertEq(address(l2PriceFeed.getPriceFeedAdapter()), priceFeedAdapter); + } + + function test_SetPriceFeedAdapter_OnlyOwner() public { + address newPriceFeedAdapter = vm.addr(uint256(bytes32("newPriceFeedAdapter"))); + address alice = address(0x1); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); + l2PriceFeed.setPriceFeedAdapter(newPriceFeedAdapter); + } + + function test_SetPriceFeedAdapter_TryToSetItTwice() public { + address newPriceFeedAdapter = vm.addr(uint256(bytes32("newPriceFeedAdapter"))); + + vm.expectRevert("L2PriceFeedLskWithoutRounds: priceFeedAdapter is already initialized"); + l2PriceFeed.setPriceFeedAdapter(newPriceFeedAdapter); } function test_TransferOwnership() public { @@ -119,7 +141,7 @@ contract L2PriceFeedLskWithoutRoundsTest is Test { assertEq(l2PriceFeedV2.decimals(), 8); assertEq(keccak256(bytes(l2PriceFeedV2.description())), keccak256(bytes("Redstone Price Feed"))); assertEq(l2PriceFeedV2.getDataFeedId(), bytes32("LSK")); - assertEq(address(l2PriceFeedV2.getPriceFeedAdapter()), address(0x19664179Ad4823C6A51035a63C9032ed27ccA441)); + assertEq(address(l2PriceFeedV2.getPriceFeedAdapter()), priceFeedAdapter); // version of L2PriceFeedLskWithoutRounds set to v2.0.0 assertEq(l2PriceFeedV2.testVersion(), "v2.0.0"); diff --git a/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol b/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol index 322043e1..19a2205c 100644 --- a/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol +++ b/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol @@ -22,6 +22,8 @@ contract L2PriceFeedUsdtWithoutRoundsTest is Test { L2PriceFeedUsdtWithoutRounds public l2PriceFeed; L2PriceFeedUsdtWithoutRounds public l2PriceFeedImplementation; + address public priceFeedAdapter = 0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0; + function setUp() public { // deploy L2PriceFeedUsdtWithoutRounds Implementation contract l2PriceFeedImplementation = new L2PriceFeedUsdtWithoutRounds(); @@ -37,7 +39,27 @@ contract L2PriceFeedUsdtWithoutRoundsTest is Test { assertEq(l2PriceFeed.decimals(), 8); assertEq(keccak256(bytes(l2PriceFeed.description())), keccak256(bytes("Redstone Price Feed"))); assertEq(l2PriceFeed.getDataFeedId(), bytes32("USDT")); - assertEq(address(l2PriceFeed.getPriceFeedAdapter()), address(0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0)); + assertEq(address(l2PriceFeed.getPriceFeedAdapter()), address(0)); + + // set PriceFeedAdapter contract address + l2PriceFeed.setPriceFeedAdapter(priceFeedAdapter); + assertEq(address(l2PriceFeed.getPriceFeedAdapter()), priceFeedAdapter); + } + + function test_SetPriceFeedAdapter_OnlyOwner() public { + address newPriceFeedAdapter = vm.addr(uint256(bytes32("newPriceFeedAdapter"))); + address alice = address(0x1); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); + l2PriceFeed.setPriceFeedAdapter(newPriceFeedAdapter); + } + + function test_SetPriceFeedAdapter_TryToSetItTwice() public { + address newPriceFeedAdapter = vm.addr(uint256(bytes32("newPriceFeedAdapter"))); + + vm.expectRevert("L2PriceFeedLskWithoutRounds: priceFeedAdapter is already initialized"); + l2PriceFeed.setPriceFeedAdapter(newPriceFeedAdapter); } function test_TransferOwnership() public { @@ -119,7 +141,7 @@ contract L2PriceFeedUsdtWithoutRoundsTest is Test { assertEq(l2PriceFeedV2.decimals(), 8); assertEq(keccak256(bytes(l2PriceFeedV2.description())), keccak256(bytes("Redstone Price Feed"))); assertEq(l2PriceFeedV2.getDataFeedId(), bytes32("USDT")); - assertEq(address(l2PriceFeedV2.getPriceFeedAdapter()), address(0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0)); + assertEq(address(l2PriceFeedV2.getPriceFeedAdapter()), priceFeedAdapter); // version of L2PriceFeedUsdtWithoutRounds set to v2.0.0 assertEq(l2PriceFeedV2.testVersion(), "v2.0.0"); From 69653ab5dc4abbd78e2a301757fcef8d16a77ab5 Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Wed, 23 Oct 2024 10:50:13 +0200 Subject: [PATCH 08/10] Remove unneeded remappings --- foundry.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/foundry.toml b/foundry.toml index 988a467a..9d52534f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -22,11 +22,9 @@ remappings = [ 'solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/', 'ERC4626/=lib/properties/lib/ERC4626/contracts/', 'properties/=lib/properties/contracts/', - 'redstone-oracles-monorepo/=lib/redstone-oracles-monorepo/', '@redstone-finance/=lib/redstone-oracles-monorepo/packages/', 'solmate/=lib/properties/lib/solmate/src/', '@chainlink=lib/chainlink/', - 'chainlink/=lib/chainlink/', ] deny_warnings = true ignored_warnings_from = [ From f6e47656b51352588790becc3230d5dede9b636e Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Thu, 24 Oct 2024 15:22:26 +0200 Subject: [PATCH 09/10] Implement getLivePrice function to return oracle values --- src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol | 8 ++++++++ src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol b/src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol index b68eb5be..57be815b 100644 --- a/src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol +++ b/src/L2/L2MultiFeedAdapterWithoutRoundsMainDemo.sol @@ -65,4 +65,12 @@ contract L2MultiFeedAdapterWithoutRoundsMainDemo is return block.timestamp > lastBlockTimestamp + 40 seconds; } } + + /// @notice This function returns numeric oracle values for a given array of data feed ids. + /// @param dataFeedIds An array of unique data feed identifiers. + /// @return An array of the extracted and verified oracle values in the same order as they are requested in the + /// dataFeedIds array and data packages timestamp. + function getLivePrice(bytes32[] memory dataFeedIds) public view virtual returns (uint256[] memory, uint256) { + return getOracleNumericValuesAndTimestampFromTxMsg(dataFeedIds); + } } diff --git a/src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol b/src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol index e696331c..f1b5be8f 100644 --- a/src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol +++ b/src/L2/L2MultiFeedAdapterWithoutRoundsPrimaryProd.sol @@ -66,4 +66,12 @@ contract L2MultiFeedAdapterWithoutRoundsPrimaryProd is return block.timestamp > lastBlockTimestamp + 40 seconds; } } + + /// @notice This function returns numeric oracle values for a given array of data feed ids. + /// @param dataFeedIds An array of unique data feed identifiers. + /// @return An array of the extracted and verified oracle values in the same order as they are requested in the + /// dataFeedIds array and data packages timestamp. + function getLivePrice(bytes32[] memory dataFeedIds) public view virtual returns (uint256[] memory, uint256) { + return getOracleNumericValuesAndTimestampFromTxMsg(dataFeedIds); + } } From 74b4c5ad135fea6183cc1c15c1a7330272897b2d Mon Sep 17 00:00:00 2001 From: Matjaz Verbole Date: Tue, 5 Nov 2024 09:15:32 +0100 Subject: [PATCH 10/10] Refactor to use factory contract to deploy L2PriceFeedWithoutRounds --- .github/workflows/pr.yaml | 2 +- script/11_deployPriceFeedsWithoutRounds.sh | 32 +++- .../L2/L2PriceFeedLskWithoutRounds.s.sol | 98 ----------- .../L2/L2PriceFeedUsdtWithoutRounds.s.sol | 102 ------------ .../L2/L2PriceFeedWithoutRounds.s.sol | 104 ++++++++++++ .../L2/L2PriceFeedWithoutRoundsFactory.s.sol | 87 ++++++++++ script/contracts/Utils.sol | 45 ++--- src/L2/L2PriceFeedUsdtWithoutRounds.sol | 59 ------- ...ounds.sol => L2PriceFeedWithoutRounds.sol} | 29 ++-- src/L2/L2PriceFeedWithoutRoundsFactory.sol | 62 +++++++ test/L2/L2PriceFeedLskWithoutRounds.t.sol | 156 ------------------ ...s.t.sol => L2PriceFeedWithoutRounds.t.sol} | 65 ++++---- test/L2/L2PriceFeedWithoutRoundsFactory.t.sol | 130 +++++++++++++++ test/utils/Utils.t.sol | 6 +- 14 files changed, 467 insertions(+), 510 deletions(-) delete mode 100644 script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol delete mode 100644 script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol create mode 100644 script/contracts/L2/L2PriceFeedWithoutRounds.s.sol create mode 100644 script/contracts/L2/L2PriceFeedWithoutRoundsFactory.s.sol delete mode 100644 src/L2/L2PriceFeedUsdtWithoutRounds.sol rename src/L2/{L2PriceFeedLskWithoutRounds.sol => L2PriceFeedWithoutRounds.sol} (75%) create mode 100644 src/L2/L2PriceFeedWithoutRoundsFactory.sol delete mode 100644 test/L2/L2PriceFeedLskWithoutRounds.t.sol rename test/L2/{L2PriceFeedUsdtWithoutRounds.t.sol => L2PriceFeedWithoutRounds.t.sol} (64%) create mode 100644 test/L2/L2PriceFeedWithoutRoundsFactory.t.sol diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index e5af8195..c600e7af 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -17,7 +17,7 @@ jobs: fail-fast: true matrix: system: - - os: macos-12 + - os: macos-latest target: x86_64-apple-darwin - os: ubuntu-latest target: x86_64-unknown-linux-gnu diff --git a/script/11_deployPriceFeedsWithoutRounds.sh b/script/11_deployPriceFeedsWithoutRounds.sh index 4b549ffb..72958cda 100755 --- a/script/11_deployPriceFeedsWithoutRounds.sh +++ b/script/11_deployPriceFeedsWithoutRounds.sh @@ -27,34 +27,50 @@ else fi echo "Done." -echo "Deploying and if enabled verifying L2PriceFeedUsdtWithoutRounds smart contract..." +echo "Deploying and if enabled verifying L2PriceFeedWithoutRoundsFactory smart contract..." if [ -z "$CONTRACT_VERIFIER" ] then - forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol:L2PriceFeedUsdtWithoutRoundsScript + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/L2PriceFeedWithoutRoundsFactory.s.sol:L2PriceFeedWithoutRoundsFactoryScript +else + if [ $CONTRACT_VERIFIER = "blockscout" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/L2PriceFeedWithoutRoundsFactory.s.sol:L2PriceFeedWithoutRoundsFactoryScript + fi + if [ $CONTRACT_VERIFIER = "etherscan" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/L2PriceFeedWithoutRoundsFactory.s.sol:L2PriceFeedWithoutRoundsFactoryScript + fi +fi +echo "Done." + +echo "Deploying and if enabled verifying L2PriceFeedWithoutRounds smart contract for USDT/USD..." +if [ -z "$CONTRACT_VERIFIER" ] +then + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv --sig "run(string,string)" script/contracts/L2/L2PriceFeedWithoutRounds.s.sol:L2PriceFeedWithoutRoundsScript "USDT" "PrimaryProd" else if [ $CONTRACT_VERIFIER = "blockscout" ] then - forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol:L2PriceFeedUsdtWithoutRoundsScript + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv --sig "run(string,string)" script/contracts/L2/L2PriceFeedWithoutRounds.s.sol:L2PriceFeedWithoutRoundsScript "USDT" "PrimaryProd" fi if [ $CONTRACT_VERIFIER = "etherscan" ] then - forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol:L2PriceFeedUsdtWithoutRoundsScript + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv --sig "run(string,string)" script/contracts/L2/L2PriceFeedWithoutRounds.s.sol:L2PriceFeedWithoutRoundsScript "USDT" "PrimaryProd" fi fi echo "Done." -echo "Deploying and if enabled verifying L2PriceFeedLskWithoutRounds smart contract..." +echo "Deploying and if enabled verifying L2PriceFeedWithoutRounds smart contract for LSK/USD..." if [ -z "$CONTRACT_VERIFIER" ] then - forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol:L2PriceFeedLskWithoutRoundsScript + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv --sig "run(string,string)" script/contracts/L2/L2PriceFeedWithoutRounds.s.sol:L2PriceFeedWithoutRoundsScript "LSK" "MainDemo" else if [ $CONTRACT_VERIFIER = "blockscout" ] then - forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol:L2PriceFeedLskWithoutRoundsScript + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv --sig "run(string,string)" script/contracts/L2/L2PriceFeedWithoutRounds.s.sol:L2PriceFeedWithoutRoundsScript "LSK" "MainDemo" fi if [ $CONTRACT_VERIFIER = "etherscan" ] then - forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol:L2PriceFeedLskWithoutRoundsScript + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv --sig "run(string,string)" script/contracts/L2/L2PriceFeedWithoutRounds.s.sol:L2PriceFeedWithoutRoundsScript "LSK" "MainDemo" fi fi echo "Done." diff --git a/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol b/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol deleted file mode 100644 index 46d273e0..00000000 --- a/script/contracts/L2/L2PriceFeedLskWithoutRounds.s.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.23; - -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import { Script, console2 } from "forge-std/Script.sol"; -import { L2PriceFeedLskWithoutRounds } from "src/L2/L2PriceFeedLskWithoutRounds.sol"; -import "script/contracts/Utils.sol"; - -/// @title L2PriceFeedLskWithoutRoundsScript - L2PriceFeedLskWithoutRounds deployment -/// script -/// @notice This contract is used to deploy L2PriceFeedLskWithoutRounds contract. -contract L2PriceFeedLskWithoutRoundsScript is Script { - /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. - Utils utils; - - function setUp() public { - utils = new Utils(); - } - - /// @notice This function deploys L2PriceFeedLskWithoutRounds contract. - function run() public { - // Deployer's private key. Owner of the L2PriceFeedLskWithoutRounds. PRIVATE_KEY is set in .env - // file. - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - - console2.log("Deploying L2PriceFeedLskWithoutRounds contract..."); - - // owner Address, the ownership of L2PriceFeedLskWithoutRounds proxy contract is transferred to - // after deployment - address ownerAddress = vm.envAddress("L2_ADAPTER_PRICEFEED_OWNER_ADDRESS"); - assert(ownerAddress != address(0)); - console2.log( - "L2 PriceFeed LSK Without Rounds contract owner address: %s (after ownership will be accepted)", - ownerAddress - ); - - // get L2MultiFeedAdapterWithoutRoundsMainDemo contract address - Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); - assert(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo != address(0)); - console2.log( - "L2 MultiFeed Adapter Without Rounds MainDemo address: %s", - l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo - ); - - // deploy L2PriceFeedLskWithoutRounds implementation contract - vm.startBroadcast(deployerPrivateKey); - L2PriceFeedLskWithoutRounds l2PriceFeedImplementation = new L2PriceFeedLskWithoutRounds(); - vm.stopBroadcast(); - - assert(address(l2PriceFeedImplementation) != address(0)); - - // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. - assert( - l2PriceFeedImplementation.proxiableUUID() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) - ); - - // deploy L2PriceFeedLskWithoutRounds proxy contract and at the same time initialize the proxy - // contract (calls the initialize function in L2PriceFeedLskWithoutRounds) - vm.startBroadcast(deployerPrivateKey); - ERC1967Proxy l2PriceFeedProxy = new ERC1967Proxy( - address(l2PriceFeedImplementation), abi.encodeWithSelector(l2PriceFeedImplementation.initialize.selector) - ); - vm.stopBroadcast(); - assert(address(l2PriceFeedProxy) != address(0)); - - // wrap in ABI to support easier calls - L2PriceFeedLskWithoutRounds l2PriceFeed = L2PriceFeedLskWithoutRounds(address(l2PriceFeedProxy)); - assert(l2PriceFeed.decimals() == 8); - assert(keccak256(bytes(l2PriceFeed.description())) == keccak256(bytes("Redstone Price Feed"))); - assert(l2PriceFeed.getDataFeedId() == bytes32("LSK")); - - // set L2MultiFeedAdapterWithoutRoundsMainDemo contract address as a PriceFeedAdapter - vm.startBroadcast(deployerPrivateKey); - l2PriceFeed.setPriceFeedAdapter(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo); - vm.stopBroadcast(); - assert(address(l2PriceFeed.getPriceFeedAdapter()) == l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo); - - // transfer ownership of L2PriceFeedLskWithoutRounds proxy; because of using - // Ownable2StepUpgradeable contract, new owner has to accept ownership - vm.startBroadcast(deployerPrivateKey); - l2PriceFeed.transferOwnership(ownerAddress); - vm.stopBroadcast(); - assert(l2PriceFeed.owner() == vm.addr(deployerPrivateKey)); // ownership is not yet accepted - - console2.log("L2 PriceFeed LSK Without Rounds contract successfully deployed!"); - console2.log("L2 PriceFeed LSK Without Rounds (Implementation) address: %s", address(l2PriceFeedImplementation)); - console2.log("L2 PriceFeed LSK Without Rounds (Proxy) address: %s", address(l2PriceFeed)); - console2.log( - "Owner of L2 PriceFeed LSK Without Rounds (Proxy) address: %s (after ownership will be accepted)", - ownerAddress - ); - - // write L2PriceFeedLskWithoutRounds address to l2addresses.json - l2AddressesConfig.L2PriceFeedLskWithoutRoundsImplementation = address(l2PriceFeedImplementation); - l2AddressesConfig.L2PriceFeedLskWithoutRounds = address(l2PriceFeed); - utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); - } -} diff --git a/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol b/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol deleted file mode 100644 index ad4f9edb..00000000 --- a/script/contracts/L2/L2PriceFeedUsdtWithoutRounds.s.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.23; - -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import { Script, console2 } from "forge-std/Script.sol"; -import { L2PriceFeedUsdtWithoutRounds } from "src/L2/L2PriceFeedUsdtWithoutRounds.sol"; -import "script/contracts/Utils.sol"; - -/// @title L2PriceFeedUsdtWithoutRoundsScript - L2PriceFeedUsdtWithoutRounds deployment -/// script -/// @notice This contract is used to deploy L2PriceFeedUsdtWithoutRounds contract. -contract L2PriceFeedUsdtWithoutRoundsScript is Script { - /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. - Utils utils; - - function setUp() public { - utils = new Utils(); - } - - /// @notice This function deploys L2PriceFeedUsdtWithoutRounds contract. - function run() public { - // Deployer's private key. Owner of the L2PriceFeedUsdtWithoutRounds. PRIVATE_KEY is set in .env - // file. - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - - console2.log("Deploying L2PriceFeedUsdtWithoutRounds contract..."); - - // owner Address, the ownership of L2PriceFeedUsdtWithoutRounds proxy contract is transferred to - // after deployment - address ownerAddress = vm.envAddress("L2_ADAPTER_PRICEFEED_OWNER_ADDRESS"); - assert(ownerAddress != address(0)); - console2.log( - "L2 PriceFeed USDT Without Rounds contract owner address: %s (after ownership will be accepted)", - ownerAddress - ); - - // get L2MultiFeedAdapterWithoutRoundsPrimaryProd contract address - Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); - assert(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd != address(0)); - console2.log( - "L2 MultiFeed Adapter Without Rounds PrimaryProd address: %s", - l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd - ); - - // deploy L2PriceFeedUsdtWithoutRounds implementation contract - vm.startBroadcast(deployerPrivateKey); - L2PriceFeedUsdtWithoutRounds l2PriceFeedImplementation = new L2PriceFeedUsdtWithoutRounds(); - vm.stopBroadcast(); - - assert(address(l2PriceFeedImplementation) != address(0)); - - // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. - assert( - l2PriceFeedImplementation.proxiableUUID() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) - ); - - // deploy L2PriceFeedUsdtWithoutRounds proxy contract and at the same time initialize the proxy - // contract (calls the initialize function in L2PriceFeedUsdtWithoutRounds) - vm.startBroadcast(deployerPrivateKey); - ERC1967Proxy l2PriceFeedProxy = new ERC1967Proxy( - address(l2PriceFeedImplementation), abi.encodeWithSelector(l2PriceFeedImplementation.initialize.selector) - ); - vm.stopBroadcast(); - assert(address(l2PriceFeedProxy) != address(0)); - - // wrap in ABI to support easier calls - L2PriceFeedUsdtWithoutRounds l2PriceFeed = L2PriceFeedUsdtWithoutRounds(address(l2PriceFeedProxy)); - assert(l2PriceFeed.decimals() == 8); - assert(keccak256(bytes(l2PriceFeed.description())) == keccak256(bytes("Redstone Price Feed"))); - assert(l2PriceFeed.getDataFeedId() == bytes32("USDT")); - - // set L2MultiFeedAdapterWithoutRoundsPrimaryProd contract address as a PriceFeedAdapter - vm.startBroadcast(deployerPrivateKey); - l2PriceFeed.setPriceFeedAdapter(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd); - vm.stopBroadcast(); - assert( - address(l2PriceFeed.getPriceFeedAdapter()) == l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd - ); - - // transfer ownership of L2PriceFeedUsdtWithoutRounds proxy; because of using - // Ownable2StepUpgradeable contract, new owner has to accept ownership - vm.startBroadcast(deployerPrivateKey); - l2PriceFeed.transferOwnership(ownerAddress); - vm.stopBroadcast(); - assert(l2PriceFeed.owner() == vm.addr(deployerPrivateKey)); // ownership is not yet accepted - - console2.log("L2 PriceFeed USDT Without Rounds contract successfully deployed!"); - console2.log( - "L2 PriceFeed USDT Without Rounds (Implementation) address: %s", address(l2PriceFeedImplementation) - ); - console2.log("L2 PriceFeed USDT Without Rounds (Proxy) address: %s", address(l2PriceFeed)); - console2.log( - "Owner of L2 PriceFeed USDT Without Rounds (Proxy) address: %s (after ownership will be accepted)", - ownerAddress - ); - - // write L2PriceFeedUsdtWithoutRounds address to l2addresses.json - l2AddressesConfig.L2PriceFeedUsdtWithoutRoundsImplementation = address(l2PriceFeedImplementation); - l2AddressesConfig.L2PriceFeedUsdtWithoutRounds = address(l2PriceFeed); - utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); - } -} diff --git a/script/contracts/L2/L2PriceFeedWithoutRounds.s.sol b/script/contracts/L2/L2PriceFeedWithoutRounds.s.sol new file mode 100644 index 00000000..9f744848 --- /dev/null +++ b/script/contracts/L2/L2PriceFeedWithoutRounds.s.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Script, console2 } from "forge-std/Script.sol"; +import { L2PriceFeedWithoutRoundsFactory } from "src/L2/L2PriceFeedWithoutRoundsFactory.sol"; +import { L2PriceFeedWithoutRounds } from "src/L2/L2PriceFeedWithoutRounds.sol"; +import "script/contracts/Utils.sol"; + +/// @title L2PriceFeedWithoutRoundsScript - L2PriceFeedWithoutRounds deployment script +/// @notice This contract is used to deploy L2PriceFeedWithoutRounds contract. +contract L2PriceFeedWithoutRoundsScript is Script { + /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. + Utils utils; + + function setUp() public { + utils = new Utils(); + } + + /// @notice This function deploys L2PriceFeedWithoutRounds contract. + function run(string memory feedId, string memory dataServiceType) public { + // Deployer's private key. Owner of the L2PriceFeedWithoutRounds. PRIVATE_KEY is set in .env file. + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + console2.log("Deploying L2PriceFeedWithoutRounds contract..."); + + // owner Address, the ownership of L2PriceFeedWithoutRounds proxy contract is transferred to after deployment + address ownerAddress = vm.envAddress("L2_ADAPTER_PRICEFEED_OWNER_ADDRESS"); + assert(ownerAddress != address(0)); + console2.log( + "L2 PriceFeed %s Without Rounds contract owner address: %s (after ownership will be accepted)", + feedId, + ownerAddress + ); + + // get L2PriceFeedWithoutRoundsFactory contract address and its instance + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); + assert(l2AddressesConfig.L2PriceFeedWithoutRoundsFactory != address(0)); + console2.log( + "L2 PriceFeed Without Rounds Factory address: %s", l2AddressesConfig.L2PriceFeedWithoutRoundsFactory + ); + L2PriceFeedWithoutRoundsFactory l2PriceFeedFactory = + L2PriceFeedWithoutRoundsFactory(l2AddressesConfig.L2PriceFeedWithoutRoundsFactory); + + if (keccak256(bytes(dataServiceType)) == keccak256(bytes("PrimaryProd"))) { + // get L2MultiFeedAdapterWithoutRoundsPrimaryProd contract address + assert(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd != address(0)); + console2.log( + "L2 MultiFeed Adapter Without Rounds PrimaryProd address: %s", + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd + ); + } else if (keccak256(bytes(dataServiceType)) == keccak256(bytes("MainDemo"))) { + // get L2MultiFeedAdapterWithoutRoundsMainDemo contract address + assert(l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo != address(0)); + console2.log( + "L2 MultiFeed Adapter Without Rounds MainDemo address: %s", + l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo + ); + } else { + assert(false); + } + + // create L2PriceFeedWithoutRounds contract + vm.startBroadcast(deployerPrivateKey); + L2PriceFeedWithoutRounds l2PriceFeed = L2PriceFeedWithoutRounds( + l2PriceFeedFactory.createL2PriceFeedWithoutRounds( + feedId, + keccak256(bytes(dataServiceType)) == keccak256(bytes("PrimaryProd")) + ? l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd + : l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo + ) + ); + vm.stopBroadcast(); + assert(address(l2PriceFeed) != address(0)); + assert(l2PriceFeed.decimals() == 8); + assert(keccak256(bytes(l2PriceFeed.description())) == keccak256(bytes("Redstone Price Feed"))); + assert(l2PriceFeed.getDataFeedId() == bytes32(abi.encodePacked(feedId))); + assert( + address(l2PriceFeed.getPriceFeedAdapter()) + == ( + keccak256(bytes(dataServiceType)) == keccak256(bytes("PrimaryProd")) + ? l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsPrimaryProd + : l2AddressesConfig.L2MultiFeedAdapterWithoutRoundsMainDemo + ) + ); + + // accept ownership and transfer ownership of L2PriceFeedWithoutRounds proxy; because of using + // Ownable2StepUpgradeable contract, new owner has to accept ownership + vm.startBroadcast(deployerPrivateKey); + l2PriceFeed.acceptOwnership(); + l2PriceFeed.transferOwnership(ownerAddress); + vm.stopBroadcast(); + assert(l2PriceFeed.owner() == vm.addr(deployerPrivateKey)); // ownership is not yet accepted + assert(l2PriceFeed.pendingOwner() == ownerAddress); + + console2.log("L2 PriceFeed %s Without Rounds contract successfully deployed!", feedId); + console2.log("L2 PriceFeed %s Without Rounds (Proxy) address: %s", feedId, address(l2PriceFeed)); + console2.log( + "Owner of L2 PriceFeed %s Without Rounds (Proxy) address: %s (after ownership will be accepted)", + feedId, + ownerAddress + ); + } +} diff --git a/script/contracts/L2/L2PriceFeedWithoutRoundsFactory.s.sol b/script/contracts/L2/L2PriceFeedWithoutRoundsFactory.s.sol new file mode 100644 index 00000000..3563d987 --- /dev/null +++ b/script/contracts/L2/L2PriceFeedWithoutRoundsFactory.s.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Script, console2 } from "forge-std/Script.sol"; +import { L2PriceFeedWithoutRoundsFactory } from "src/L2/L2PriceFeedWithoutRoundsFactory.sol"; +import "script/contracts/Utils.sol"; + +/// @title L2PriceFeedWithoutRoundsFactoryScript - L2PriceFeedWithoutRoundsFactory deployment script +/// @notice This contract is used to deploy L2PriceFeedWithoutRoundsFactory contract. +contract L2PriceFeedWithoutRoundsFactoryScript is Script { + /// @notice Utils contract which provides functions to read and write JSON files containing L2 addresses. + Utils utils; + + function setUp() public { + utils = new Utils(); + } + + /// @notice This function deploys L2PriceFeedWithoutRoundsFactory contract. + function run() public { + // Deployer's private key. Owner of the L2PriceFeedWithoutRoundsFactory. PRIVATE_KEY is set in .env + // file. + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + console2.log("Deploying L2PriceFeedWithoutRoundsFactory contract..."); + + // owner Address, the ownership of L2PriceFeedWithoutRoundsFactory proxy contract is transferred to + // after deployment + address ownerAddress = vm.envAddress("L2_ADAPTER_PRICEFEED_OWNER_ADDRESS"); + assert(ownerAddress != address(0)); + console2.log( + "L2 PriceFeed Without Rounds Factory contract owner address: %s (after ownership will be accepted)", + ownerAddress + ); + + // deploy L2PriceFeedWithoutRoundsFactory implementation contract + vm.startBroadcast(deployerPrivateKey); + L2PriceFeedWithoutRoundsFactory l2PriceFeedFactoryImplementation = new L2PriceFeedWithoutRoundsFactory(); + vm.stopBroadcast(); + + assert(address(l2PriceFeedFactoryImplementation) != address(0)); + + // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + assert( + l2PriceFeedFactoryImplementation.proxiableUUID() + == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) + ); + + // deploy L2PriceFeedWithoutRoundsFactory proxy contract and at the same time initialize the proxy contract + // (calls the initialize function in L2PriceFeedWithoutRoundsFactory) + vm.startBroadcast(deployerPrivateKey); + ERC1967Proxy l2PriceFeedFactoryProxy = new ERC1967Proxy( + address(l2PriceFeedFactoryImplementation), + abi.encodeWithSelector(l2PriceFeedFactoryImplementation.initialize.selector) + ); + vm.stopBroadcast(); + assert(address(l2PriceFeedFactoryProxy) != address(0)); + + // wrap in ABI to support easier calls + L2PriceFeedWithoutRoundsFactory l2PriceFeedFactory = + L2PriceFeedWithoutRoundsFactory(address(l2PriceFeedFactoryProxy)); + + // transfer ownership of L2PriceFeedWithoutRoundsFactory proxy; because of using Ownable2StepUpgradeable + // contract, new owner has to accept ownership + vm.startBroadcast(deployerPrivateKey); + l2PriceFeedFactory.transferOwnership(ownerAddress); + vm.stopBroadcast(); + assert(l2PriceFeedFactory.owner() == vm.addr(deployerPrivateKey)); // ownership is not yet accepted + + console2.log("L2 PriceFeed Without Rounds Factory contract successfully deployed!"); + console2.log( + "L2 PriceFeed Without Rounds Factory (Implementation) address: %s", + address(l2PriceFeedFactoryImplementation) + ); + console2.log("L2 PriceFeed Without Rounds Factory (Proxy) address: %s", address(l2PriceFeedFactory)); + console2.log( + "Owner of L2 PriceFeed Without Rounds Factory (Proxy) address: %s (after ownership will be accepted)", + ownerAddress + ); + + // write L2PriceFeedWithoutRoundsFactory address to l2addresses.json + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(utils.getL2AddressesFilePath()); + l2AddressesConfig.L2PriceFeedWithoutRoundsFactoryImplementation = address(l2PriceFeedFactoryImplementation); + l2AddressesConfig.L2PriceFeedWithoutRoundsFactory = address(l2PriceFeedFactory); + utils.writeL2AddressesFile(l2AddressesConfig, utils.getL2AddressesFilePath()); + } +} diff --git a/script/contracts/Utils.sol b/script/contracts/Utils.sol index 99e173aa..5fa3fb2a 100644 --- a/script/contracts/Utils.sol +++ b/script/contracts/Utils.sol @@ -51,14 +51,10 @@ contract Utils is Script { address L2MultiFeedAdapterWithoutRoundsPrimaryProd; /// @notice The current implementation of L2 MultiFeedAdapterWithoutRoundsPrimaryProd Contract. address L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation; - /// @notice L2 PriceFeedLskWithoutRounds address. - address L2PriceFeedLskWithoutRounds; - /// @notice The current implementation of L2 PriceFeedLskWithoutRounds Contract. - address L2PriceFeedLskWithoutRoundsImplementation; - /// @notice L2 PriceFeedUsdtWithoutRounds address. - address L2PriceFeedUsdtWithoutRounds; - /// @notice The current implementation of L2 PriceFeedUsdtWithoutRounds Contract. - address L2PriceFeedUsdtWithoutRoundsImplementation; + /// @notice L2 PriceFeedWithoutRoundsFactory address. + address L2PriceFeedWithoutRoundsFactory; + /// @notice The current implementation of L2 PriceFeedWithoutRoundsFactory Contract. + address L2PriceFeedWithoutRoundsFactoryImplementation; /// @notice L2 Reward contract (in Proxy), which users interact with. address L2Reward; /// @notice The current implementation of L2 Reward contract. @@ -270,28 +266,17 @@ contract Utils is Script { l2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation; } catch { } - try vm.parseJsonAddress(addressJson, ".L2PriceFeedLskWithoutRounds") returns ( - address l2PriceFeedLskWithoutRounds + try vm.parseJsonAddress(addressJson, ".L2PriceFeedWithoutRoundsFactory") returns ( + address l2PriceFeedWithoutRoundsFactory ) { - l2AddressesConfig.L2PriceFeedLskWithoutRounds = l2PriceFeedLskWithoutRounds; + l2AddressesConfig.L2PriceFeedWithoutRoundsFactory = l2PriceFeedWithoutRoundsFactory; } catch { } - try vm.parseJsonAddress(addressJson, ".L2PriceFeedLskWithoutRoundsImplementation") returns ( - address l2PriceFeedLskWithoutRoundsImplementation + try vm.parseJsonAddress(addressJson, ".L2PriceFeedWithoutRoundsFactoryImplementation") returns ( + address l2PriceFeedWithoutRoundsFactoryImplementation ) { - l2AddressesConfig.L2PriceFeedLskWithoutRoundsImplementation = l2PriceFeedLskWithoutRoundsImplementation; - } catch { } - - try vm.parseJsonAddress(addressJson, ".L2PriceFeedUsdtWithoutRounds") returns ( - address l2PriceFeedUsdtWithoutRounds - ) { - l2AddressesConfig.L2PriceFeedUsdtWithoutRounds = l2PriceFeedUsdtWithoutRounds; - } catch { } - - try vm.parseJsonAddress(addressJson, ".L2PriceFeedUsdtWithoutRoundsImplementation") returns ( - address l2PriceFeedUsdtWithoutRoundsImplementation - ) { - l2AddressesConfig.L2PriceFeedUsdtWithoutRoundsImplementation = l2PriceFeedUsdtWithoutRoundsImplementation; + l2AddressesConfig.L2PriceFeedWithoutRoundsFactoryImplementation = + l2PriceFeedWithoutRoundsFactoryImplementation; } catch { } try vm.parseJsonAddress(addressJson, ".L2RewardImplementation") returns (address l2RewardImplementation) { @@ -376,13 +361,9 @@ contract Utils is Script { "L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation", cfg.L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation ); - vm.serializeAddress(json, "L2PriceFeedLskWithoutRounds", cfg.L2PriceFeedLskWithoutRounds); - vm.serializeAddress( - json, "L2PriceFeedLskWithoutRoundsImplementation", cfg.L2PriceFeedLskWithoutRoundsImplementation - ); - vm.serializeAddress(json, "L2PriceFeedUsdtWithoutRounds", cfg.L2PriceFeedUsdtWithoutRounds); + vm.serializeAddress(json, "L2PriceFeedWithoutRoundsFactory", cfg.L2PriceFeedWithoutRoundsFactory); vm.serializeAddress( - json, "L2PriceFeedUsdtWithoutRoundsImplementation", cfg.L2PriceFeedUsdtWithoutRoundsImplementation + json, "L2PriceFeedWithoutRoundsFactoryImplementation", cfg.L2PriceFeedWithoutRoundsFactoryImplementation ); vm.serializeAddress(json, "L2Reward", cfg.L2Reward); vm.serializeAddress(json, "L2RewardImplementation", cfg.L2RewardImplementation); diff --git a/src/L2/L2PriceFeedUsdtWithoutRounds.sol b/src/L2/L2PriceFeedUsdtWithoutRounds.sol deleted file mode 100644 index 2c71ae67..00000000 --- a/src/L2/L2PriceFeedUsdtWithoutRounds.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.23; - -import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; -import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import { PriceFeedWithoutRoundsForMultiFeedAdapter } from - "@redstone-finance/on-chain-relayer/contracts/price-feeds/without-rounds/PriceFeedWithoutRoundsForMultiFeedAdapter.sol"; -import { IRedstoneAdapter } from "@redstone-finance/on-chain-relayer/contracts/core/IRedstoneAdapter.sol"; - -/// @title L2PriceFeedUsdtWithoutRounds - L2PriceFeedUsdtWithoutRounds contract -/// @notice This contract represents PriceFeedWithoutRoundsForMultiFeedAdapter contract for USDT data feed. -contract L2PriceFeedUsdtWithoutRounds is - Initializable, - Ownable2StepUpgradeable, - UUPSUpgradeable, - PriceFeedWithoutRoundsForMultiFeedAdapter -{ - /// @notice The address of the MultiFeedAdapter contract. - address internal priceFeedAdapter; - - /// @notice Disabling initializers on implementation contract to prevent misuse. - constructor() { - _disableInitializers(); - } - - /// @notice Setting global params. - function initialize() public virtual override initializer { - super.initialize(); - __Ownable2Step_init(); - __Ownable_init(msg.sender); - } - - /// @notice Ensures that only the owner can authorize a contract upgrade. It reverts if called by any address other - /// than the contract owner. - /// @param _newImplementation The address of the new contract implementation to which the proxy will be upgraded. - function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner { } - - /// @notice This function returns the data feed ID. - /// @return The data feed ID. - function getDataFeedId() public view virtual override returns (bytes32) { - return bytes32("USDT"); - } - - /// @notice This function returns the price feed adapter. - /// @return The price feed adapter. - function getPriceFeedAdapter() public view virtual override returns (IRedstoneAdapter) { - return IRedstoneAdapter(priceFeedAdapter); - } - - /// @notice This function sets the address of the MultiFeedAdapter contract. - /// @param _adapter The address of the MultiFeedAdapter contract. - function setPriceFeedAdapter(address _adapter) public virtual onlyOwner { - require(priceFeedAdapter == address(0), "L2PriceFeedLskWithoutRounds: priceFeedAdapter is already initialized"); - require(_adapter != address(0), "L2PriceFeedLskWithoutRounds: priceFeedAdapter address can not be zero"); - - priceFeedAdapter = _adapter; - } -} diff --git a/src/L2/L2PriceFeedLskWithoutRounds.sol b/src/L2/L2PriceFeedWithoutRounds.sol similarity index 75% rename from src/L2/L2PriceFeedLskWithoutRounds.sol rename to src/L2/L2PriceFeedWithoutRounds.sol index 7282d50f..d335efb6 100644 --- a/src/L2/L2PriceFeedLskWithoutRounds.sol +++ b/src/L2/L2PriceFeedWithoutRounds.sol @@ -8,9 +8,9 @@ import { PriceFeedWithoutRoundsForMultiFeedAdapter } from "@redstone-finance/on-chain-relayer/contracts/price-feeds/without-rounds/PriceFeedWithoutRoundsForMultiFeedAdapter.sol"; import { IRedstoneAdapter } from "@redstone-finance/on-chain-relayer/contracts/core/IRedstoneAdapter.sol"; -/// @title L2PriceFeedLskWithoutRounds - L2PriceFeedLskWithoutRounds contract -/// @notice This contract represents PriceFeedWithoutRoundsForMultiFeedAdapter contract for LSK data feed. -contract L2PriceFeedLskWithoutRounds is +/// @title L2PriceFeedWithoutRounds - L2PriceFeedWithoutRounds contract +/// @notice This contract represents PriceFeedWithoutRoundsForMultiFeedAdapter contract. +contract L2PriceFeedWithoutRounds is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable, @@ -19,16 +19,24 @@ contract L2PriceFeedLskWithoutRounds is /// @notice The address of the MultiFeedAdapter contract. address internal priceFeedAdapter; + /// @notice The data feed ID. + string internal dataFeedId; + /// @notice Disabling initializers on implementation contract to prevent misuse. constructor() { _disableInitializers(); } /// @notice Setting global params. - function initialize() public virtual override initializer { - super.initialize(); + /// @param _feedId The data feed ID. + /// @param _adapter The address of the MultiFeedAdapter contract. + function initialize(string memory _feedId, address _adapter) public virtual initializer { + require(bytes(_feedId).length > 0, "L2PriceFeedWithoutRounds: data feed ID can not be empty"); + require(_adapter != address(0), "L2PriceFeedWithoutRounds: adapter contract address can not be zero"); __Ownable2Step_init(); __Ownable_init(msg.sender); + dataFeedId = _feedId; + priceFeedAdapter = _adapter; } /// @notice Ensures that only the owner can authorize a contract upgrade. It reverts if called by any address other @@ -39,7 +47,7 @@ contract L2PriceFeedLskWithoutRounds is /// @notice This function returns the data feed ID. /// @return The data feed ID. function getDataFeedId() public view virtual override returns (bytes32) { - return bytes32("LSK"); + return bytes32(abi.encodePacked(dataFeedId)); } /// @notice This function returns the price feed adapter. @@ -47,13 +55,4 @@ contract L2PriceFeedLskWithoutRounds is function getPriceFeedAdapter() public view virtual override returns (IRedstoneAdapter) { return IRedstoneAdapter(priceFeedAdapter); } - - /// @notice This function sets the address of the MultiFeedAdapter contract. - /// @param _adapter The address of the MultiFeedAdapter contract. - function setPriceFeedAdapter(address _adapter) public virtual onlyOwner { - require(priceFeedAdapter == address(0), "L2PriceFeedLskWithoutRounds: priceFeedAdapter is already initialized"); - require(_adapter != address(0), "L2PriceFeedLskWithoutRounds: priceFeedAdapter address can not be zero"); - - priceFeedAdapter = _adapter; - } } diff --git a/src/L2/L2PriceFeedWithoutRoundsFactory.sol b/src/L2/L2PriceFeedWithoutRoundsFactory.sol new file mode 100644 index 00000000..8f6b281e --- /dev/null +++ b/src/L2/L2PriceFeedWithoutRoundsFactory.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { L2PriceFeedWithoutRounds } from "./L2PriceFeedWithoutRounds.sol"; + +/// @title L2PriceFeedWithoutRoundsFactory - L2PriceFeedWithoutRoundsFactory contract +/// @notice This contract is a factory contract that generates L2PriceFeedWithoutRounds contracts on the network it's +/// deployed to. It simplifies the process of creating new L2PriceFeedWithoutRounds contracts. +contract L2PriceFeedWithoutRoundsFactory is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable { + /// @notice The array of L2PriceFeedWithoutRounds contract addresses. + address[] public l2PriceFeedWithoutRoundsContracts; + + /// @notice The mapping of L2PriceFeedWithoutRounds contract addresses to array of data feed IDs. + mapping(address => string[]) public l2PriceFeedWithoutRoundsDataFeedIds; + + /// @notice Disabling initializers on implementation contract to prevent misuse. + constructor() { + _disableInitializers(); + } + + /// @notice Setting global params. + function initialize() public virtual initializer { + __Ownable2Step_init(); + __Ownable_init(msg.sender); + } + + /// @notice Ensures that only the owner can authorize a contract upgrade. It reverts if called by any address other + /// than the contract owner. + /// @param _newImplementation The address of the new contract implementation to which the proxy will be upgraded. + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner { } + + /// @notice This function creates a new L2PriceFeedWithoutRounds contract. + /// @param _feedId The data feed ID. + /// @param _adapter The address of the MultiFeedAdapter contract. + /// @return The address of the newly created L2PriceFeedWithoutRounds contract. + function createL2PriceFeedWithoutRounds(string memory _feedId, address _adapter) public virtual returns (address) { + require(bytes(_feedId).length > 0, "L2PriceFeedWithoutRoundsFactory: data feed ID can not be empty"); + require(_adapter != address(0), "L2PriceFeedWithoutRoundsFactory: adapter contract address can not be zero"); + + // deploy L2PriceFeedWithoutRounds implementation contract + L2PriceFeedWithoutRounds newL2PriceFeedImplementation = new L2PriceFeedWithoutRounds(); + require(address(newL2PriceFeedImplementation) != address(0)); + + // deploy L2PriceFeedWithoutRounds contract via proxy and initialize it + ERC1967Proxy newL2PriceFeedProxy = new ERC1967Proxy(address(newL2PriceFeedImplementation), ""); + L2PriceFeedWithoutRounds newL2PriceFeed = L2PriceFeedWithoutRounds(address(newL2PriceFeedProxy)); + require(address(newL2PriceFeed) != address(0)); + newL2PriceFeed.initialize(_feedId, _adapter); + + // transfer ownership to the caller + newL2PriceFeed.transferOwnership(msg.sender); + + l2PriceFeedWithoutRoundsContracts.push(address(newL2PriceFeed)); + l2PriceFeedWithoutRoundsDataFeedIds[address(newL2PriceFeed)].push(_feedId); + + return address(newL2PriceFeed); + } +} diff --git a/test/L2/L2PriceFeedLskWithoutRounds.t.sol b/test/L2/L2PriceFeedLskWithoutRounds.t.sol deleted file mode 100644 index 7cfa76ee..00000000 --- a/test/L2/L2PriceFeedLskWithoutRounds.t.sol +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.23; - -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; -import { Test } from "forge-std/Test.sol"; -import { L2PriceFeedLskWithoutRounds } from "src/L2/L2PriceFeedLskWithoutRounds.sol"; - -contract L2PriceFeedLskWithoutRoundsV2Mock is L2PriceFeedLskWithoutRounds { - string public testVersion; - - function initializeV2(string memory _version) public reinitializer(2) { - testVersion = _version; - } - - function onlyV2() public pure returns (string memory) { - return "Hello from V2"; - } -} - -contract L2PriceFeedLskWithoutRoundsTest is Test { - L2PriceFeedLskWithoutRounds public l2PriceFeed; - L2PriceFeedLskWithoutRounds public l2PriceFeedImplementation; - - address public priceFeedAdapter = 0x19664179Ad4823C6A51035a63C9032ed27ccA441; - - function setUp() public { - // deploy L2PriceFeedLskWithoutRounds Implementation contract - l2PriceFeedImplementation = new L2PriceFeedLskWithoutRounds(); - - // deploy L2PriceFeedLskWithoutRounds contract via Proxy and initialize it at the same time - l2PriceFeed = L2PriceFeedLskWithoutRounds( - address( - new ERC1967Proxy( - address(l2PriceFeedImplementation), abi.encodeWithSelector(l2PriceFeed.initialize.selector) - ) - ) - ); - assertEq(l2PriceFeed.decimals(), 8); - assertEq(keccak256(bytes(l2PriceFeed.description())), keccak256(bytes("Redstone Price Feed"))); - assertEq(l2PriceFeed.getDataFeedId(), bytes32("LSK")); - assertEq(address(l2PriceFeed.getPriceFeedAdapter()), address(0)); - - // set PriceFeedAdapter contract address - l2PriceFeed.setPriceFeedAdapter(priceFeedAdapter); - assertEq(address(l2PriceFeed.getPriceFeedAdapter()), priceFeedAdapter); - } - - function test_SetPriceFeedAdapter_OnlyOwner() public { - address newPriceFeedAdapter = vm.addr(uint256(bytes32("newPriceFeedAdapter"))); - address alice = address(0x1); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - l2PriceFeed.setPriceFeedAdapter(newPriceFeedAdapter); - } - - function test_SetPriceFeedAdapter_TryToSetItTwice() public { - address newPriceFeedAdapter = vm.addr(uint256(bytes32("newPriceFeedAdapter"))); - - vm.expectRevert("L2PriceFeedLskWithoutRounds: priceFeedAdapter is already initialized"); - l2PriceFeed.setPriceFeedAdapter(newPriceFeedAdapter); - } - - function test_TransferOwnership() public { - address newOwner = vm.addr(1); - - l2PriceFeed.transferOwnership(newOwner); - assertEq(l2PriceFeed.owner(), address(this)); - - vm.prank(newOwner); - l2PriceFeed.acceptOwnership(); - assertEq(l2PriceFeed.owner(), newOwner); - } - - function testFuzz_TransferOwnership_RevertWhenNotCalledByOwner(uint256 _addressSeed) public { - _addressSeed = bound(_addressSeed, 1, type(uint160).max); - address nobody = vm.addr(_addressSeed); - address newOwner = vm.addr(1); - - if (nobody == address(this)) { - return; - } - - // owner is this contract - assertEq(l2PriceFeed.owner(), address(this)); - - // address nobody is not the owner so it cannot call transferOwnership - vm.startPrank(nobody); - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); - l2PriceFeed.transferOwnership(newOwner); - vm.stopPrank(); - } - - function testFuzz_TransferOwnership_RevertWhenNotCalledByPendingOwner(uint256 _addressSeed) public { - address newOwner = vm.addr(1); - - l2PriceFeed.transferOwnership(newOwner); - assertEq(l2PriceFeed.owner(), address(this)); - - _addressSeed = bound(_addressSeed, 1, type(uint160).max); - address nobody = vm.addr(_addressSeed); - - if (nobody == newOwner) { - return; - } - vm.prank(nobody); - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); - l2PriceFeed.acceptOwnership(); - } - - function testFuzz_UpgradeToAndCall_RevertWhenNotOwner(uint256 _addressSeed) public { - // deploy L2PriceFeedLskWithoutRoundsV2Mock implementation contract - L2PriceFeedLskWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedLskWithoutRoundsV2Mock(); - _addressSeed = bound(_addressSeed, 1, type(uint160).max); - address nobody = vm.addr(_addressSeed); - - if (nobody == address(this)) { - return; - } - - vm.prank(nobody); - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); - l2PriceFeed.upgradeToAndCall(address(l2PriceFeedV2Implementation), ""); - } - - function test_UpgradeToAndCall_SuccessUpgrade() public { - // deploy L2PriceFeedLskWithoutRoundsV2Mock implementation contract - L2PriceFeedLskWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedLskWithoutRoundsV2Mock(); - - // upgrade contract, and also change some variables by reinitialize - l2PriceFeed.upgradeToAndCall( - address(l2PriceFeedV2Implementation), - abi.encodeWithSelector(l2PriceFeedV2Implementation.initializeV2.selector, "v2.0.0") - ); - - // wrap L2PriceFeedLskWithoutRounds proxy with new contract - L2PriceFeedLskWithoutRoundsV2Mock l2PriceFeedV2 = L2PriceFeedLskWithoutRoundsV2Mock(address(l2PriceFeed)); - - // check if the upgrade was successful and the variables are the same - assertEq(l2PriceFeedV2.decimals(), 8); - assertEq(keccak256(bytes(l2PriceFeedV2.description())), keccak256(bytes("Redstone Price Feed"))); - assertEq(l2PriceFeedV2.getDataFeedId(), bytes32("LSK")); - assertEq(address(l2PriceFeedV2.getPriceFeedAdapter()), priceFeedAdapter); - - // version of L2PriceFeedLskWithoutRounds set to v2.0.0 - assertEq(l2PriceFeedV2.testVersion(), "v2.0.0"); - - // new function introduced - assertEq(l2PriceFeedV2.onlyV2(), "Hello from V2"); - - // assure cannot re-reinitialize - vm.expectRevert(); - l2PriceFeedV2.initializeV2("v3.0.0"); - } -} diff --git a/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol b/test/L2/L2PriceFeedWithoutRounds.t.sol similarity index 64% rename from test/L2/L2PriceFeedUsdtWithoutRounds.t.sol rename to test/L2/L2PriceFeedWithoutRounds.t.sol index 19a2205c..62371efa 100644 --- a/test/L2/L2PriceFeedUsdtWithoutRounds.t.sol +++ b/test/L2/L2PriceFeedWithoutRounds.t.sol @@ -4,9 +4,10 @@ pragma solidity 0.8.23; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; import { Test } from "forge-std/Test.sol"; -import { L2PriceFeedUsdtWithoutRounds } from "src/L2/L2PriceFeedUsdtWithoutRounds.sol"; +import { L2PriceFeedWithoutRoundsFactory } from "src/L2/L2PriceFeedWithoutRoundsFactory.sol"; +import { L2PriceFeedWithoutRounds } from "src/L2/L2PriceFeedWithoutRounds.sol"; -contract L2PriceFeedUsdtWithoutRoundsV2Mock is L2PriceFeedUsdtWithoutRounds { +contract L2PriceFeedWithoutRoundsV2Mock is L2PriceFeedWithoutRounds { string public testVersion; function initializeV2(string memory _version) public reinitializer(2) { @@ -18,48 +19,42 @@ contract L2PriceFeedUsdtWithoutRoundsV2Mock is L2PriceFeedUsdtWithoutRounds { } } -contract L2PriceFeedUsdtWithoutRoundsTest is Test { - L2PriceFeedUsdtWithoutRounds public l2PriceFeed; - L2PriceFeedUsdtWithoutRounds public l2PriceFeedImplementation; +contract L2PriceFeedWithoutRoundsTest is Test { + L2PriceFeedWithoutRounds public l2PriceFeed; + L2PriceFeedWithoutRounds public l2PriceFeedImplementation; address public priceFeedAdapter = 0x1038999DCf0A302Cc8Eed72fAeCbf0eEBfC476b0; function setUp() public { - // deploy L2PriceFeedUsdtWithoutRounds Implementation contract - l2PriceFeedImplementation = new L2PriceFeedUsdtWithoutRounds(); + // deploy L2PriceFeedWithoutRoundsFactory Implementation contract + L2PriceFeedWithoutRoundsFactory l2PriceFeedFactoryImplementation = new L2PriceFeedWithoutRoundsFactory(); - // deploy L2PriceFeedUsdtWithoutRounds contract via Proxy and initialize it at the same time - l2PriceFeed = L2PriceFeedUsdtWithoutRounds( + // deploy L2PriceFeedWithoutRoundsFactory contract via Proxy and initialize it at the same time + L2PriceFeedWithoutRoundsFactory l2PriceFeedFactory; + l2PriceFeedFactory = L2PriceFeedWithoutRoundsFactory( address( new ERC1967Proxy( - address(l2PriceFeedImplementation), abi.encodeWithSelector(l2PriceFeed.initialize.selector) + address(l2PriceFeedFactoryImplementation), + abi.encodeWithSelector(l2PriceFeedFactory.initialize.selector) ) ) ); + + // create L2PriceFeedWithoutRounds contract + l2PriceFeed = + L2PriceFeedWithoutRounds(l2PriceFeedFactory.createL2PriceFeedWithoutRounds("USDT", priceFeedAdapter)); + assert(address(l2PriceFeed) != address(0)); assertEq(l2PriceFeed.decimals(), 8); assertEq(keccak256(bytes(l2PriceFeed.description())), keccak256(bytes("Redstone Price Feed"))); assertEq(l2PriceFeed.getDataFeedId(), bytes32("USDT")); - assertEq(address(l2PriceFeed.getPriceFeedAdapter()), address(0)); - - // set PriceFeedAdapter contract address - l2PriceFeed.setPriceFeedAdapter(priceFeedAdapter); assertEq(address(l2PriceFeed.getPriceFeedAdapter()), priceFeedAdapter); - } - - function test_SetPriceFeedAdapter_OnlyOwner() public { - address newPriceFeedAdapter = vm.addr(uint256(bytes32("newPriceFeedAdapter"))); - address alice = address(0x1); - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice)); - l2PriceFeed.setPriceFeedAdapter(newPriceFeedAdapter); - } - - function test_SetPriceFeedAdapter_TryToSetItTwice() public { - address newPriceFeedAdapter = vm.addr(uint256(bytes32("newPriceFeedAdapter"))); + // accept ownership + l2PriceFeed.acceptOwnership(); - vm.expectRevert("L2PriceFeedLskWithoutRounds: priceFeedAdapter is already initialized"); - l2PriceFeed.setPriceFeedAdapter(newPriceFeedAdapter); + // check L2PriceFeedWithoutRoundsFactory variables + assertEq(l2PriceFeedFactory.l2PriceFeedWithoutRoundsContracts(0), address(l2PriceFeed)); + assertEq(l2PriceFeedFactory.l2PriceFeedWithoutRoundsDataFeedIds(address(l2PriceFeed), 0), "USDT"); } function test_TransferOwnership() public { @@ -110,8 +105,8 @@ contract L2PriceFeedUsdtWithoutRoundsTest is Test { } function testFuzz_UpgradeToAndCall_RevertWhenNotOwner(uint256 _addressSeed) public { - // deploy L2PriceFeedUsdtWithoutRoundsV2Mock implementation contract - L2PriceFeedUsdtWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedUsdtWithoutRoundsV2Mock(); + // deploy L2PriceFeedWithoutRoundsV2Mock implementation contract + L2PriceFeedWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedWithoutRoundsV2Mock(); _addressSeed = bound(_addressSeed, 1, type(uint160).max); address nobody = vm.addr(_addressSeed); @@ -125,8 +120,8 @@ contract L2PriceFeedUsdtWithoutRoundsTest is Test { } function test_UpgradeToAndCall_SuccessUpgrade() public { - // deploy L2PriceFeedUsdtWithoutRoundsV2Mock implementation contract - L2PriceFeedUsdtWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedUsdtWithoutRoundsV2Mock(); + // deploy L2PriceFeedWithoutRoundsV2Mock implementation contract + L2PriceFeedWithoutRoundsV2Mock l2PriceFeedV2Implementation = new L2PriceFeedWithoutRoundsV2Mock(); // upgrade contract, and also change some variables by reinitialize l2PriceFeed.upgradeToAndCall( @@ -134,8 +129,8 @@ contract L2PriceFeedUsdtWithoutRoundsTest is Test { abi.encodeWithSelector(l2PriceFeedV2Implementation.initializeV2.selector, "v2.0.0") ); - // wrap L2PriceFeedUsdtWithoutRounds proxy with new contract - L2PriceFeedUsdtWithoutRoundsV2Mock l2PriceFeedV2 = L2PriceFeedUsdtWithoutRoundsV2Mock(address(l2PriceFeed)); + // wrap L2PriceFeedWithoutRounds proxy with new contract + L2PriceFeedWithoutRoundsV2Mock l2PriceFeedV2 = L2PriceFeedWithoutRoundsV2Mock(address(l2PriceFeed)); // check if the upgrade was successful and the variables are the same assertEq(l2PriceFeedV2.decimals(), 8); @@ -143,7 +138,7 @@ contract L2PriceFeedUsdtWithoutRoundsTest is Test { assertEq(l2PriceFeedV2.getDataFeedId(), bytes32("USDT")); assertEq(address(l2PriceFeedV2.getPriceFeedAdapter()), priceFeedAdapter); - // version of L2PriceFeedUsdtWithoutRounds set to v2.0.0 + // version of L2PriceFeedWithoutRounds set to v2.0.0 assertEq(l2PriceFeedV2.testVersion(), "v2.0.0"); // new function introduced diff --git a/test/L2/L2PriceFeedWithoutRoundsFactory.t.sol b/test/L2/L2PriceFeedWithoutRoundsFactory.t.sol new file mode 100644 index 00000000..b11a98f8 --- /dev/null +++ b/test/L2/L2PriceFeedWithoutRoundsFactory.t.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import { Test } from "forge-std/Test.sol"; +import { L2PriceFeedWithoutRoundsFactory } from "src/L2/L2PriceFeedWithoutRoundsFactory.sol"; + +contract L2PriceFeedWithoutRoundsFactoryV2Mock is L2PriceFeedWithoutRoundsFactory { + string public testVersion; + + function initializeV2(string memory _version) public reinitializer(2) { + testVersion = _version; + } + + function onlyV2() public pure returns (string memory) { + return "Hello from V2"; + } +} + +contract L2PriceFeedWithoutRoundsFactoryTest is Test { + L2PriceFeedWithoutRoundsFactory public l2PriceFeedFactory; + L2PriceFeedWithoutRoundsFactory public l2PriceFeedFactoryImplementation; + + address public priceFeedAdapter = 0x19664179Ad4823C6A51035a63C9032ed27ccA441; + + function setUp() public { + // deploy L2PriceFeedWithoutRoundsFactory Implementation contract + l2PriceFeedFactoryImplementation = new L2PriceFeedWithoutRoundsFactory(); + + // deploy L2PriceFeedWithoutRoundsFactory contract via Proxy and initialize it at the same time + l2PriceFeedFactory = L2PriceFeedWithoutRoundsFactory( + address( + new ERC1967Proxy( + address(l2PriceFeedFactoryImplementation), + abi.encodeWithSelector(l2PriceFeedFactory.initialize.selector) + ) + ) + ); + } + + function test_TransferOwnership() public { + address newOwner = vm.addr(1); + + l2PriceFeedFactory.transferOwnership(newOwner); + assertEq(l2PriceFeedFactory.owner(), address(this)); + + vm.prank(newOwner); + l2PriceFeedFactory.acceptOwnership(); + assertEq(l2PriceFeedFactory.owner(), newOwner); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByOwner(uint256 _addressSeed) public { + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + address newOwner = vm.addr(1); + + if (nobody == address(this)) { + return; + } + + // owner is this contract + assertEq(l2PriceFeedFactory.owner(), address(this)); + + // address nobody is not the owner so it cannot call transferOwnership + vm.startPrank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeedFactory.transferOwnership(newOwner); + vm.stopPrank(); + } + + function testFuzz_TransferOwnership_RevertWhenNotCalledByPendingOwner(uint256 _addressSeed) public { + address newOwner = vm.addr(1); + + l2PriceFeedFactory.transferOwnership(newOwner); + assertEq(l2PriceFeedFactory.owner(), address(this)); + + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == newOwner) { + return; + } + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeedFactory.acceptOwnership(); + } + + function testFuzz_UpgradeToAndCall_RevertWhenNotOwner(uint256 _addressSeed) public { + // deploy L2PriceFeedWithoutRoundsFactoryV2Mock implementation contract + L2PriceFeedWithoutRoundsFactoryV2Mock l2PriceFeedFactoryV2Implementation = + new L2PriceFeedWithoutRoundsFactoryV2Mock(); + _addressSeed = bound(_addressSeed, 1, type(uint160).max); + address nobody = vm.addr(_addressSeed); + + if (nobody == address(this)) { + return; + } + + vm.prank(nobody); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, nobody)); + l2PriceFeedFactory.upgradeToAndCall(address(l2PriceFeedFactoryV2Implementation), ""); + } + + function test_UpgradeToAndCall_SuccessUpgrade() public { + // deploy L2PriceFeedWithoutRoundsFactoryV2Mock implementation contract + L2PriceFeedWithoutRoundsFactoryV2Mock l2PriceFeedFactoryV2Implementation = + new L2PriceFeedWithoutRoundsFactoryV2Mock(); + + // upgrade contract, and also change some variables by reinitialize + l2PriceFeedFactory.upgradeToAndCall( + address(l2PriceFeedFactoryV2Implementation), + abi.encodeWithSelector(l2PriceFeedFactoryV2Implementation.initializeV2.selector, "v2.0.0") + ); + + // wrap L2PriceFeedWithoutRoundsFactory proxy with new contract + L2PriceFeedWithoutRoundsFactoryV2Mock l2PriceFeedFactoryV2 = + L2PriceFeedWithoutRoundsFactoryV2Mock(address(l2PriceFeedFactory)); + + // version of L2PriceFeedWithoutRoundsFactory set to v2.0.0 + assertEq(l2PriceFeedFactoryV2.testVersion(), "v2.0.0"); + + // new function introduced + assertEq(l2PriceFeedFactoryV2.onlyV2(), "Hello from V2"); + + // assure cannot re-reinitialize + vm.expectRevert(); + l2PriceFeedFactoryV2.initializeV2("v3.0.0"); + } +} diff --git a/test/utils/Utils.t.sol b/test/utils/Utils.t.sol index 92f371b6..1447383a 100644 --- a/test/utils/Utils.t.sol +++ b/test/utils/Utils.t.sol @@ -52,10 +52,8 @@ contract UtilsTest is Test { L2MultiFeedAdapterWithoutRoundsMainDemoImplementation: address(index++), L2MultiFeedAdapterWithoutRoundsPrimaryProd: address(index++), L2MultiFeedAdapterWithoutRoundsPrimaryProdImplementation: address(index++), - L2PriceFeedLskWithoutRounds: address(index++), - L2PriceFeedLskWithoutRoundsImplementation: address(index++), - L2PriceFeedUsdtWithoutRounds: address(index++), - L2PriceFeedUsdtWithoutRoundsImplementation: address(index++), + L2PriceFeedWithoutRoundsFactory: address(index++), + L2PriceFeedWithoutRoundsFactoryImplementation: address(index++), L2Reward: address(index++), L2RewardImplementation: address(index++), L2RewardPaused: address(index++),