From fba78a101f80e993a35f03a5feffdf9a251559fb Mon Sep 17 00:00:00 2001 From: Matt Rice Date: Mon, 20 May 2024 03:56:59 -0400 Subject: [PATCH] add coinbase Signed-off-by: Matt Rice --- src/factories/StandardChainlinkFactory.sol | 2 +- src/factories/StandardCoinbaseFactory.sol | 58 ++++++++++++++++++++++ test/unit/StandardCoinbaseFactory.sol | 51 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/factories/StandardCoinbaseFactory.sol create mode 100644 test/unit/StandardCoinbaseFactory.sol diff --git a/src/factories/StandardChainlinkFactory.sol b/src/factories/StandardChainlinkFactory.sol index 2f54d8c..35c40f4 100644 --- a/src/factories/StandardChainlinkFactory.sol +++ b/src/factories/StandardChainlinkFactory.sol @@ -22,7 +22,7 @@ contract OvalChainlink is MutableUnlockersController, ChainlinkSourceAdapter, Ch ) ChainlinkSourceAdapter(source) MutableUnlockersController(lockWindow, maxTraversal, unlockers) - ChainlinkDestinationAdapter(source.decimals()) + ChainlinkDestinationAdapter(18) { _transferOwnership(owner); } diff --git a/src/factories/StandardCoinbaseFactory.sol b/src/factories/StandardCoinbaseFactory.sol new file mode 100644 index 0000000..7d0f8b0 --- /dev/null +++ b/src/factories/StandardCoinbaseFactory.sol @@ -0,0 +1,58 @@ +// 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 {CoinbaseSourceAdapter} from "../adapters/source-adapters/CoinbaseSourceAdapter.sol"; +import {ChainlinkDestinationAdapter} from "../adapters/destination-adapters/ChainlinkDestinationAdapter.sol"; +import {IAggregatorV3SourceCoinbase} from "../interfaces/coinbase/IAggregatorV3SourceCoinbase.sol"; +import {BaseFactory} from "./BaseFactory.sol"; + +/** + * @title OvalCoinbase is the recommended Oval Coinbase contract that allows Oval to extract OEV generated by + * Coinbase usage. + */ +contract OvalCoinbase is MutableUnlockersController, CoinbaseSourceAdapter, ChainlinkDestinationAdapter { + constructor( + IAggregatorV3SourceCoinbase source, + string memory ticker, + address[] memory unlockers, + uint256 lockWindow, + uint256 maxTraversal, + address owner + ) + CoinbaseSourceAdapter(source, ticker) + MutableUnlockersController(lockWindow, maxTraversal, unlockers) + ChainlinkDestinationAdapter(18) + { + _transferOwnership(owner); + } +} + +/** + * @title StandardCoinbaseFactory is the recommended factory for use cases that want a Coinbase 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. + */ +contract StandardCoinbaseFactory is Ownable, BaseFactory { + IAggregatorV3SourceCoinbase immutable SOURCE; + + constructor(IAggregatorV3SourceCoinbase source, uint256 maxTraversal, address[] memory _defaultUnlockers) + BaseFactory(maxTraversal, _defaultUnlockers) + { + SOURCE = source; + } + + /** + * @notice Creates the Coinbase Oval instance. + * @param ticker the Coinbase oracle's ticker. + * @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(string memory ticker, uint256 lockWindow) external returns (address oval) { + oval = address(new OvalCoinbase(SOURCE, ticker, defaultUnlockers, lockWindow, MAX_TRAVERSAL, owner())); + emit OvalDeployed(msg.sender, oval, lockWindow, MAX_TRAVERSAL, owner(), defaultUnlockers); + } +} diff --git a/test/unit/StandardCoinbaseFactory.sol b/test/unit/StandardCoinbaseFactory.sol new file mode 100644 index 0000000..91cac2c --- /dev/null +++ b/test/unit/StandardCoinbaseFactory.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.17; + +import {StandardCoinbaseFactory} from "../../src/factories/StandardCoinbaseFactory.sol"; +import {OvalCoinbase} from "../../src/factories/StandardCoinbaseFactory.sol"; +import {IAggregatorV3SourceCoinbase} from "../../src/interfaces/coinbase/IAggregatorV3SourceCoinbase.sol"; +import {MockChainlinkV3Aggregator} from "../mocks/MockChainlinkV3Aggregator.sol"; +import {CommonTest} from "../Common.sol"; + +contract StandardCoinbaseFactoryTest is CommonTest { + StandardCoinbaseFactory factory; + IAggregatorV3SourceCoinbase mockSource; + address[] unlockers; + uint256 lockWindow = 300; + uint256 maxTraversal = 15; + string ticker = "test ticker"; + + function setUp() public { + mockSource = IAggregatorV3SourceCoinbase(address(new MockChainlinkV3Aggregator(8, 420))); + unlockers.push(address(0x123)); + factory = new StandardCoinbaseFactory(mockSource, maxTraversal, unlockers); + } + + function testCreateMutableUnlockerOvalCoinbase() public { + address created = factory.create(ticker, lockWindow); + + assertTrue(created != address(0)); // Check if the address is set, non-zero. + + OvalCoinbase instance = OvalCoinbase(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(ticker, lockWindow); + OvalCoinbase instance = OvalCoinbase(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)); + } +}