diff --git a/src/factories/BaseFactory.sol b/src/factories/BaseFactory.sol index 52879b4..3715964 100644 --- a/src/factories/BaseFactory.sol +++ b/src/factories/BaseFactory.sol @@ -14,6 +14,7 @@ contract BaseFactory is Ownable { address[] public defaultUnlockers; event DefaultUnlockersSet(address[] defaultUnlockers); + event OvalDeployed(address indexed deployer, address indexed oval, uint256 indexed lockWindow, uint256 maxTraversal, address owner, address[] unlockers); constructor(uint256 maxTraversal, address[] memory _defaultUnlockers) { MAX_TRAVERSAL = maxTraversal; diff --git a/src/factories/StandardChainlinkFactory.sol b/src/factories/StandardChainlinkFactory.sol index 5e69534..0cf82a7 100644 --- a/src/factories/StandardChainlinkFactory.sol +++ b/src/factories/StandardChainlinkFactory.sol @@ -9,13 +9,13 @@ import {IAggregatorV3Source} from "../interfaces/chainlink/IAggregatorV3Source.s import {BaseFactory} from "./BaseFactory.sol"; /** - * @title StandardOvalChainlink is the recommended Oval Chainlink contract that allows Oval to extract OEV generated by + * @title OvalChainlink is the recommended Oval Chainlink contract that allows Oval to extract OEV generated by * Chainlink usage. */ -contract StandardOvalChainlink is MutableUnlockersController, ChainlinkSourceAdapter, ChainlinkDestinationAdapter { - constructor(IAggregatorV3Source source, address[] memory unlockers, uint256 _lockWindow, uint256 _maxTraversal, address owner) +contract OvalChainlink is MutableUnlockersController, ChainlinkSourceAdapter, ChainlinkDestinationAdapter { + constructor(IAggregatorV3Source source, address[] memory unlockers, uint256 lockWindow, uint256 maxTraversal, address owner) ChainlinkSourceAdapter(source) - MutableUnlockersController(_lockWindow, _maxTraversal, unlockers) + MutableUnlockersController(lockWindow, maxTraversal, unlockers) ChainlinkDestinationAdapter(source.decimals()) { _transferOwnership(owner); @@ -23,7 +23,7 @@ contract StandardOvalChainlink is MutableUnlockersController, ChainlinkSourceAda } /** - * @title StandardChainlinkFactory is the reccomended factory for use cases that want a Chainlink source and Chainlink + * @title StandardChainlinkFactory is the recommended factory for use cases that want a Chainlink source and Chainlink * interface. * @dev This is the best factory for most use cases, but there are other variants that may be needed if different * mutability choices are desired. @@ -36,8 +36,10 @@ contract StandardChainlinkFactory is Ownable, BaseFactory { * @param source the Chainlink oracle source contract. * @param lockWindow the lockWindow used for this Oval instance. This is the length of the window * for the Oval auction to be run and, thus, the maximum time that prices will be delayed. + * @return oval deployed oval address. */ - function create(IAggregatorV3Source source, uint256 lockWindow) external returns (address) { - return address(new StandardOvalChainlink(source, defaultUnlockers, lockWindow, MAX_TRAVERSAL, owner())); + function create(IAggregatorV3Source source, uint256 lockWindow) external returns (address oval) { + oval = address(new OvalChainlink(source, defaultUnlockers, lockWindow, MAX_TRAVERSAL, owner())); + emit OvalDeployed(msg.sender, oval, lockWindow, MAX_TRAVERSAL, owner(), defaultUnlockers); } } diff --git a/src/factories/StandardChronicleFactory.sol b/src/factories/StandardChronicleFactory.sol new file mode 100644 index 0000000..8be7b51 --- /dev/null +++ b/src/factories/StandardChronicleFactory.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.17; + +import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; +import {MutableUnlockersController} from "../controllers/MutableUnlockersController.sol"; +import {ChronicleMedianSourceAdapter} from "../adapters/source-adapters/ChronicleMedianSourceAdapter.sol"; +import {ChainlinkDestinationAdapter} from "../adapters/destination-adapters/ChainlinkDestinationAdapter.sol"; +import {IAggregatorV3Source} from "../interfaces/chainlink/IAggregatorV3Source.sol"; +import {BaseFactory} from "./BaseFactory.sol"; +import {IMedian} from "../interfaces/chronicle/IMedian.sol"; + +/** + * @title OvalChronicle is the reccomended Oval contract that allows Oval to extract OEV generated by + * Chronicle price feeds and allow usage via the Chainlink interface. + */ +contract OvalChronicle is MutableUnlockersController, ChronicleMedianSourceAdapter, ChainlinkDestinationAdapter { + constructor(IMedian source, address[] memory unlockers, uint256 lockWindow, uint256 maxTraversal, address owner) + ChronicleMedianSourceAdapter(source) + MutableUnlockersController(lockWindow, maxTraversal, unlockers) + ChainlinkDestinationAdapter(18) + { + _transferOwnership(owner); + } +} + +/** + * @title StandardPythFactory is the recommended factory for use cases that want a Chronicle source and Chainlink + * interface. + * @dev This is the best factory for most use cases that need a Chronicle source, but there are other variants that may be + * needed if different mutability or interface choices are desired. + */ +contract StandardChronicleFactory is Ownable, BaseFactory { + constructor(uint256 maxTraversal, address[] memory _defaultUnlockers) BaseFactory(maxTraversal, _defaultUnlockers) {} + + /** + * @notice Creates the Pyth Oval instance. + * @param chronicle Chronicle source contract. + * @param lockWindow the lockWindow used for this Oval instance. This is the length of the window + * for the Oval auction to be run and, thus, the maximum time that prices will be delayed. + * @return oval deployed oval address. + */ + function create(IMedian chronicle, uint256 lockWindow) external returns (address oval) { + oval = address(new OvalChronicle(chronicle, defaultUnlockers, lockWindow, MAX_TRAVERSAL, owner())); + emit OvalDeployed(msg.sender, oval, lockWindow, MAX_TRAVERSAL, owner(), defaultUnlockers); + } +} diff --git a/src/factories/StandardPythFactory.sol b/src/factories/StandardPythFactory.sol index 73717ad..c2cc1b5 100644 --- a/src/factories/StandardPythFactory.sol +++ b/src/factories/StandardPythFactory.sol @@ -1,39 +1,50 @@ -// // SPDX-License-Identifier: BUSL-1.1 -// pragma solidity 0.8.17; +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.17; -// import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; -// import {MutableUnlockersController} from "../controllers/MutableUnlockersController.sol"; -// import {PythSourceAdapter} from "../adapters/source-adapters/PythSourceAdapter.sol"; -// import {ChainlinkDestinationAdapter} from "../adapters/destination-adapters/ChainlinkDestinationAdapter.sol"; -// import {IAggregatorV3Source} from "../interfaces/chainlink/IAggregatorV3Source.sol"; -// import {BaseFactory} from "./BaseFactory.sol"; +import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; +import {MutableUnlockersController} from "../controllers/MutableUnlockersController.sol"; +import {PythSourceAdapter} from "../adapters/source-adapters/PythSourceAdapter.sol"; +import {ChainlinkDestinationAdapter} from "../adapters/destination-adapters/ChainlinkDestinationAdapter.sol"; +import {IAggregatorV3Source} from "../interfaces/chainlink/IAggregatorV3Source.sol"; +import {BaseFactory} from "./BaseFactory.sol"; +import {IPyth} from "../interfaces/pyth/IPyth.sol"; -// /** -// * @title OvalPyth is the reccomended Oval contract that allows Oval to extract OEV generated by -// * Pyth price feeds and allow usage via the Chainlink interface. -// */ -// contract OvalPyth is MutableUnlockersController, PythSourceAdapter, ChainlinkDestinationAdapter { -// constructor(IAggregatorV3Source source, address[] memory unlockers, uint256 _lockWindow, uint256 _maxTraversal, address owner) -// PythSourceAdapter(source) -// MutableUnlockersController(_lockWindow, _maxTraversal, unlockers) -// ChainlinkDestinationAdapter(source.decimals()) -// { -// _transferOwnership(owner); -// } -// } +/** + * @title OvalPyth is the reccomended Oval contract that allows Oval to extract OEV generated by + * Pyth price feeds and allow usage via the Chainlink interface. + */ +contract OvalPyth is MutableUnlockersController, PythSourceAdapter, ChainlinkDestinationAdapter { + constructor(IPyth source, bytes32 pythPriceId, address[] memory unlockers, uint256 lockWindow, uint256 maxTraversal, address owner) + PythSourceAdapter(source, pythPriceId) + MutableUnlockersController(lockWindow, maxTraversal, unlockers) + ChainlinkDestinationAdapter(18) + { + _transferOwnership(owner); + } +} -// /** -// * @title StandardChainlinkFactory is the reccomended factory for Chainlink source/destination contracts that allow -// * Oval to extract OEV generated by Chainlink usage. -// */ -// contract StandardChainlinkFactory is Ownable { -// /** -// * @notice Creates the Chainlink Oval instance. -// * @param source the Chainlink oracle source contract. -// * @param lockWindow the lockWindow used for this Oval instance. This is the length of the window -// * for the Oval auction to be run and, thus, the maximum time that prices will be delayed. -// */ -// function create(IAggregatorV3Source source, uint256 lockWindow) external returns (address) { -// return address(new OvalChainlink(source, defaultUnlockers, lockWindow, MAX_TRAVERSAL, owner())); -// } -// } +/** + * @title StandardPythFactory is the recommended factory for use cases that want a Pyth source and Chainlink + * interface. + * @dev This is the best factory for most use cases that need a Pyth source, but there are other variants that may be + * needed if different mutability or interface choices are desired. + */ +contract StandardPythFactory is Ownable, BaseFactory { + IPyth immutable pyth; + + constructor(IPyth _pyth, uint256 maxTraversal, address[] memory _defaultUnlockers) BaseFactory(maxTraversal, _defaultUnlockers) { + pyth = _pyth; + } + + /** + * @notice Creates the Pyth Oval instance. + * @param pythPriceId the Pyth price id. + * @param lockWindow the lockWindow used for this Oval instance. This is the length of the window + * for the Oval auction to be run and, thus, the maximum time that prices will be delayed. + * @return oval deployed oval address. + */ + function create(bytes32 pythPriceId, uint256 lockWindow) external returns (address oval) { + oval = address(new OvalPyth(pyth, pythPriceId, defaultUnlockers, lockWindow, MAX_TRAVERSAL, owner())); + emit OvalDeployed(msg.sender, oval, lockWindow, MAX_TRAVERSAL, owner(), defaultUnlockers); + } +} diff --git a/test/unit/MutableUnlockersOvalChainlinkFactory.sol b/test/unit/StandardChainlinkFactory.sol similarity index 89% rename from test/unit/MutableUnlockersOvalChainlinkFactory.sol rename to test/unit/StandardChainlinkFactory.sol index fe3bc05..f7fec96 100644 --- a/test/unit/MutableUnlockersOvalChainlinkFactory.sol +++ b/test/unit/StandardChainlinkFactory.sol @@ -1,5 +1,5 @@ import {StandardChainlinkFactory} from "../../src/factories/StandardChainlinkFactory.sol"; -import {StandardOvalChainlink} from "../../src/factories/StandardChainlinkFactory.sol"; +import {OvalChainlink} from "../../src/factories/StandardChainlinkFactory.sol"; import {IAggregatorV3Source} from "../../src/interfaces/chainlink/IAggregatorV3Source.sol"; import {MockChainlinkV3Aggregator} from "../mocks/MockChainlinkV3Aggregator.sol"; import {CommonTest} from "../Common.sol"; @@ -24,7 +24,7 @@ contract StandardChainlinkFactoryTest is CommonTest { assertTrue(created != address(0)); // Check if the address is set, non-zero. - StandardOvalChainlink instance = StandardOvalChainlink(created); + OvalChainlink instance = OvalChainlink(created); assertTrue(instance.lockWindow() == lockWindow); assertTrue(instance.maxTraversal() == maxTraversal); @@ -39,7 +39,7 @@ contract StandardChainlinkFactoryTest is CommonTest { address created = factory.create( IAggregatorV3Source(address(mockSource)), lockWindow ); - StandardOvalChainlink instance = StandardOvalChainlink(created); + OvalChainlink instance = OvalChainlink(created); address newUnlocker = address(0x789); instance.setUnlocker(newUnlocker, true); // Correct method to add unlockers diff --git a/test/unit/StandardChronicleFactory.sol b/test/unit/StandardChronicleFactory.sol new file mode 100644 index 0000000..7d5982b --- /dev/null +++ b/test/unit/StandardChronicleFactory.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.17; + +import {StandardChronicleFactory} from "../../src/factories/StandardChronicleFactory.sol"; +import {OvalChronicle} from "../../src/factories/StandardChronicleFactory.sol"; +import {IMedian} from "../../src/interfaces/chronicle/IMedian.sol"; +import {CommonTest} from "../Common.sol"; + +contract StandardChronicleFactoryTest is CommonTest { + StandardChronicleFactory factory; + IMedian mockSource; + address[] unlockers; + uint256 lockWindow = 300; + uint256 maxTraversal = 15; + + function setUp() public { + mockSource = IMedian(address(0x456)); + unlockers.push(address(0x123)); + factory = new StandardChronicleFactory(maxTraversal, unlockers); + } + + function testCreateMutableUnlockerOvalChronicle() public { + address created = factory.create( + mockSource, lockWindow + ); + + assertTrue(created != address(0)); // Check if the address is set, non-zero. + + OvalChronicle instance = OvalChronicle(created); + assertTrue(instance.lockWindow() == lockWindow); + assertTrue(instance.maxTraversal() == maxTraversal); + + // Check if the unlockers are set correctly + for (uint256 i = 0; i < unlockers.length; i++) { + assertTrue(instance.canUnlock(unlockers[i], 0)); + } + assertFalse(instance.canUnlock(address(0x456), 0)); // Check if a random address cannot unlock + } + + function testOwnerCanChangeUnlockers() public { + address created = factory.create( + mockSource, lockWindow + ); + OvalChronicle instance = OvalChronicle(created); + + address newUnlocker = address(0x789); + instance.setUnlocker(newUnlocker, true); // Correct method to add unlockers + assertTrue(instance.canUnlock(newUnlocker, 0)); + + instance.setUnlocker(address(0x123), false); // Correct method to remove unlockers + assertFalse(instance.canUnlock(address(0x123), 0)); + } +} diff --git a/test/unit/StandardPythFactory.sol b/test/unit/StandardPythFactory.sol new file mode 100644 index 0000000..24b035a --- /dev/null +++ b/test/unit/StandardPythFactory.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.17; + +import {StandardPythFactory} from "../../src/factories/StandardPythFactory.sol"; +import {OvalPyth} from "../../src/factories/StandardPythFactory.sol"; +import {IPyth} from "../../src/interfaces/pyth/IPyth.sol"; +import {CommonTest} from "../Common.sol"; + +contract StandardPythFactoryTest is CommonTest { + StandardPythFactory factory; + IPyth mockSource; + address[] unlockers; + uint256 lockWindow = 300; + uint256 maxTraversal = 15; + + function setUp() public { + mockSource = IPyth(address(0x456)); + unlockers.push(address(0x123)); + factory = new StandardPythFactory(mockSource, maxTraversal, unlockers); + } + + function testCreateMutableUnlockerOvalPyth() public { + address created = factory.create( + bytes32(uint256(0x789)), lockWindow + ); + + assertTrue(created != address(0)); // Check if the address is set, non-zero. + + OvalPyth instance = OvalPyth(created); + assertTrue(instance.lockWindow() == lockWindow); + assertTrue(instance.maxTraversal() == maxTraversal); + + // Check if the unlockers are set correctly + for (uint256 i = 0; i < unlockers.length; i++) { + assertTrue(instance.canUnlock(unlockers[i], 0)); + } + assertFalse(instance.canUnlock(address(0x456), 0)); // Check if a random address cannot unlock + } + + function testOwnerCanChangeUnlockers() public { + address created = factory.create( + bytes32(uint256(0x789)), lockWindow + ); + OvalPyth instance = OvalPyth(created); + + address newUnlocker = address(0x789); + instance.setUnlocker(newUnlocker, true); // Correct method to add unlockers + assertTrue(instance.canUnlock(newUnlocker, 0)); + + instance.setUnlocker(address(0x123), false); // Correct method to remove unlockers + assertFalse(instance.canUnlock(address(0x123), 0)); + } +}