diff --git a/package.json b/package.json index 7124f34..eb82655 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "package.json": "sort-package-json" }, "dependencies": { - "@defi-wonderland/prophet-core-contracts": "0.0.0-c25103ea", - "@defi-wonderland/prophet-modules-contracts": "0.0.0-ee716ee0" + "@defi-wonderland/prophet-core-contracts": "0.0.0-438de1c5", + "@defi-wonderland/prophet-modules-contracts": "0.0.0-1197c328" }, "devDependencies": { "@commitlint/cli": "19.3.0", diff --git a/remappings.txt b/remappings.txt index 0ba2eec..2245399 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,7 @@ forge-std/=node_modules/forge-std/src halmos-cheatcodes=node_modules/halmos-cheatcodes +@defi-wonderland/prophet-core-contracts/=node_modules/@defi-wonderland/prophet-core-contracts +@defi-wonderland/prophet-modules-contracts/=node_modules/@defi-wonderland/prophet-modules-contracts contracts/=src/contracts interfaces/=src/interfaces diff --git a/src/contracts/Arbitrable.sol b/src/contracts/Arbitrable.sol new file mode 100644 index 0000000..ada965e --- /dev/null +++ b/src/contracts/Arbitrable.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IArbitrable} from 'interfaces/IArbitrable.sol'; + +/** + * @title Arbitrable + * @notice Makes a contract subject to arbitration by The Graph + */ +abstract contract Arbitrable is IArbitrable { + address private _arbitrator; + address private _council; + address private _pendingCouncil; + + /** + * @notice Checks that the caller is The Graph's Arbitrator + */ + modifier onlyArbitrator() { + if (msg.sender != _arbitrator) revert Arbitrable_OnlyArbitrator(); + _; + } + + /** + * @notice Checks that the caller is The Graph's Council + */ + modifier onlyCouncil() { + if (msg.sender != _council) revert Arbitrable_OnlyCouncil(); + _; + } + + /** + * @notice Checks that the caller is the pending The Graph's Council + */ + modifier onlyPendingCouncil() { + if (msg.sender != _pendingCouncil) revert Arbitrable_OnlyPendingCouncil(); + _; + } + + /** + * @notice Constructor + * @param __arbitrator The address of The Graph's Arbitrator + * @param __council The address of The Graph's Council + */ + constructor(address __arbitrator, address __council) { + _setArbitrator(__arbitrator); + _setCouncil(__council); + } + + /// @inheritdoc IArbitrable + function arbitrator() public view returns (address __arbitrator) { + __arbitrator = _arbitrator; + } + + /// @inheritdoc IArbitrable + function council() public view returns (address __council) { + __council = _council; + } + + /// @inheritdoc IArbitrable + function pendingCouncil() public view returns (address __pendingCouncil) { + __pendingCouncil = _pendingCouncil; + } + + /// @inheritdoc IArbitrable + function setArbitrator(address __arbitrator) external onlyCouncil { + _setArbitrator(__arbitrator); + } + + /// @inheritdoc IArbitrable + function setPendingCouncil(address __pendingCouncil) external onlyCouncil { + _setPendingCouncil(__pendingCouncil); + } + + /// @inheritdoc IArbitrable + function confirmCouncil() external onlyPendingCouncil { + _setCouncil(_pendingCouncil); + delete _pendingCouncil; + } + + /** + * @notice Sets the address of The Graph's Arbitrator + * @param __arbitrator The address of The Graph's Arbitrator + */ + function _setArbitrator(address __arbitrator) private { + _arbitrator = __arbitrator; + emit SetArbitrator(__arbitrator); + } + + /** + * @notice Sets the address of The Graph's Council + * @param __council The address of The Graph's Council + */ + function _setCouncil(address __council) private { + _council = __council; + emit SetCouncil(__council); + } + + /** + * @notice Sets the address of the pending The Graph's Council + * @param __pendingCouncil The address of the pending The Graph's Council + */ + function _setPendingCouncil(address __pendingCouncil) private { + _pendingCouncil = __pendingCouncil; + emit SetPendingCouncil(__pendingCouncil); + } +} diff --git a/src/contracts/EBORequestCreator.sol b/src/contracts/EBORequestCreator.sol index 327a0de..1013926 100644 --- a/src/contracts/EBORequestCreator.sol +++ b/src/contracts/EBORequestCreator.sol @@ -65,7 +65,7 @@ contract EBORequestCreator is IEBORequestCreator { if (requestIdPerChainAndEpoch[_chainIds[_i]][_epoch] == bytes32(0)) { // TODO: COMPLETE THE REQUEST CREATION WITH THE PROPER MODULES IOracle.Request memory _request = IOracle.Request({ - nonce: 0, + nonce: uint96(0), requester: address(this), requestModule: _requestData.requestModule, responseModule: _requestData.responseModule, diff --git a/src/interfaces/IArbitrable.sol b/src/interfaces/IArbitrable.sol new file mode 100644 index 0000000..5d98b9e --- /dev/null +++ b/src/interfaces/IArbitrable.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/** + * @title Arbitrable + * @notice Makes a contract subject to arbitration by The Graph + */ +interface IArbitrable { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emitted when The Graph's Arbitrator is set + * @param _arbitrator The address of The Graph's Arbitrator + */ + event SetArbitrator(address _arbitrator); + + /** + * @notice Emitted when The Graph's Council is set + * @param _council The address of The Graph's Council + */ + event SetCouncil(address _council); + + /** + * @notice Emitted when the pending The Graph's Council is set + * @param _pendingCouncil The address of the pending The Graph's Council + */ + event SetPendingCouncil(address _pendingCouncil); + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Thrown when the caller is not The Graph's Arbitrator + */ + error Arbitrable_OnlyArbitrator(); + + /** + * @notice Thrown when the caller is not The Graph's Council + */ + error Arbitrable_OnlyCouncil(); + + /** + * @notice Thrown when the caller is not the pending The Graph's Council + */ + error Arbitrable_OnlyPendingCouncil(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Returns the address of The Graph's Arbitrator + * @return _arbitrator The address of The Graph's Arbitrator + */ + function arbitrator() external view returns (address _arbitrator); + + /** + * @notice Returns the address of The Graph's Council + * @return _council The address of The Graph's Council + */ + function council() external view returns (address _council); + + /** + * @notice Returns the address of the pending The Graph's Council + * @return _pendingCouncil The address of the pending The Graph's Council + */ + function pendingCouncil() external view returns (address _pendingCouncil); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Changes the address of The Graph's Arbitrator + * @dev Callable only by The Graph's Council + * @param _arbitrator The address of The Graph's Arbitrator + */ + function setArbitrator(address _arbitrator) external; + + /** + * @notice Sets the address of the pending The Graph's Council + * @dev Callable only by The Graph's Council + * @param _pendingCouncil The address of the pending The Graph's Council + */ + function setPendingCouncil(address _pendingCouncil) external; + + /** + * @notice Changes the address of The Graph's Council to the pending one + * @dev Callable only by the pending The Graph's Council + */ + function confirmCouncil() external; +} diff --git a/test/integration/IntegrationBase.sol b/test/integration/IntegrationBase.sol index 08303fa..d24e0f9 100644 --- a/test/integration/IntegrationBase.sol +++ b/test/integration/IntegrationBase.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.26; import {Test} from 'forge-std/Test.sol'; import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {Oracle} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Oracle.sol'; import {EBORequestCreator, IEBORequestCreator, IOracle} from 'contracts/EBORequestCreator.sol'; contract IntegrationBase is Test { @@ -20,6 +21,7 @@ contract IntegrationBase is Test { vm.createSelectFork(vm.rpcUrl('mainnet'), _FORK_BLOCK); vm.startPrank(_owner); + _oracle = new Oracle(); _eboRequestCreator = new EBORequestCreator(_oracle, _arbitrator); vm.stopPrank(); diff --git a/test/unit/Arbitrable.t.sol b/test/unit/Arbitrable.t.sol new file mode 100644 index 0000000..a8433c1 --- /dev/null +++ b/test/unit/Arbitrable.t.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IArbitrable} from 'interfaces/IArbitrable.sol'; + +import {Arbitrable} from 'contracts/Arbitrable.sol'; + +import 'forge-std/Test.sol'; + +contract ArbitrableMock is Arbitrable { + constructor(address _arbitrator, address _council) Arbitrable(_arbitrator, _council) {} + + // solhint-disable-next-line no-empty-blocks + function mockOnlyArbitrator() external onlyArbitrator {} + + // solhint-disable-next-line no-empty-blocks + function mockOnlyCouncil() external onlyCouncil {} + + // solhint-disable-next-line no-empty-blocks + function mockOnlyPendingCouncil() external onlyPendingCouncil {} +} + +contract Arbitrable_Unit_BaseTest is Test { + using stdStorage for StdStorage; + + ArbitrableMock public arbitrable; + + address public arbitrator; + address public council; + address public pendingCouncil; + + event SetArbitrator(address _arbitrator); + event SetCouncil(address _council); + event SetPendingCouncil(address _pendingCouncil); + + function setUp() public { + arbitrator = makeAddr('Arbitrator'); + council = makeAddr('Council'); + pendingCouncil = makeAddr('PendingCouncil'); + + arbitrable = new ArbitrableMock(arbitrator, council); + } + + function _mockPendingCouncil(address _pendingCouncil) internal { + stdstore.target(address(arbitrable)).sig(IArbitrable.pendingCouncil.selector).checked_write(_pendingCouncil); + } +} + +contract Arbitrable_Unit_Constructor is Arbitrable_Unit_BaseTest { + function test_setArbitrator(address _arbitrator, address _council) public { + arbitrable = new ArbitrableMock(_arbitrator, _council); + + assertEq(arbitrable.arbitrator(), _arbitrator); + } + + function test_emitSetArbitrator(address _arbitrator, address _council) public { + vm.expectEmit(); + emit SetArbitrator(_arbitrator); + new ArbitrableMock(_arbitrator, _council); + } + + function test_setCouncil(address _arbitrator, address _council) public { + arbitrable = new ArbitrableMock(_arbitrator, _council); + + assertEq(arbitrable.council(), _council); + } + + function test_emitSetCouncil(address _arbitrator, address _council) public { + vm.expectEmit(); + emit SetCouncil(_council); + new ArbitrableMock(_arbitrator, _council); + } +} + +contract Arbitrable_Unit_SetArbitrator is Arbitrable_Unit_BaseTest { + modifier happyPath() { + vm.startPrank(council); + _; + } + + function test_revertOnlyCouncil(address _arbitrator) public happyPath { + vm.stopPrank(); + vm.expectRevert(IArbitrable.Arbitrable_OnlyCouncil.selector); + arbitrable.setArbitrator(_arbitrator); + } + + function test_setArbitrator(address _arbitrator) public happyPath { + arbitrable.setArbitrator(_arbitrator); + + assertEq(arbitrable.arbitrator(), _arbitrator); + } + + function test_emitSetArbitrator(address _arbitrator) public happyPath { + vm.expectEmit(); + emit SetArbitrator(_arbitrator); + arbitrable.setArbitrator(_arbitrator); + } +} + +contract Arbitrable_Unit_SetPendingCouncil is Arbitrable_Unit_BaseTest { + modifier happyPath() { + vm.startPrank(council); + _; + } + + function test_revertOnlyCouncil(address _pendingCouncil) public happyPath { + vm.stopPrank(); + vm.expectRevert(IArbitrable.Arbitrable_OnlyCouncil.selector); + arbitrable.setPendingCouncil(_pendingCouncil); + } + + function test_setPendingCouncil(address _pendingCouncil) public happyPath { + arbitrable.setPendingCouncil(_pendingCouncil); + + assertEq(arbitrable.pendingCouncil(), _pendingCouncil); + } + + function test_emitSetPendingCouncil(address _pendingCouncil) public happyPath { + vm.expectEmit(); + emit SetPendingCouncil(_pendingCouncil); + arbitrable.setPendingCouncil(_pendingCouncil); + } +} + +contract Arbitrable_Unit_ConfirmCouncil is Arbitrable_Unit_BaseTest { + modifier happyPath() { + _mockPendingCouncil(pendingCouncil); + + vm.startPrank(pendingCouncil); + _; + } + + function test_revertOnlyPendingCouncil() public happyPath { + vm.stopPrank(); + vm.expectRevert(IArbitrable.Arbitrable_OnlyPendingCouncil.selector); + arbitrable.confirmCouncil(); + } + + function test_setCouncil() public happyPath { + arbitrable.confirmCouncil(); + + assertEq(arbitrable.council(), pendingCouncil); + } + + function test_emitSetCouncil() public happyPath { + vm.expectEmit(); + emit SetCouncil(pendingCouncil); + arbitrable.confirmCouncil(); + } + + function test_deletePendingCouncil() public happyPath { + arbitrable.confirmCouncil(); + + assertEq(arbitrable.pendingCouncil(), address(0)); + } +} + +contract Arbitrable_Unit_OnlyArbitrator is Arbitrable_Unit_BaseTest { + modifier happyPath() { + vm.startPrank(arbitrator); + _; + } + + function test_revertOnlyArbitrator() public { + vm.stopPrank(); + vm.expectRevert(IArbitrable.Arbitrable_OnlyArbitrator.selector); + arbitrable.mockOnlyArbitrator(); + } + + function test_onlyArbitrator() public happyPath { + arbitrable.mockOnlyArbitrator(); + } +} + +contract Arbitrable_Unit_OnlyCouncil is Arbitrable_Unit_BaseTest { + modifier happyPath() { + vm.startPrank(council); + _; + } + + function test_revertOnlyCouncil() public { + vm.stopPrank(); + vm.expectRevert(IArbitrable.Arbitrable_OnlyCouncil.selector); + arbitrable.mockOnlyCouncil(); + } + + function test_onlyCouncil() public happyPath { + arbitrable.mockOnlyCouncil(); + } +} + +contract Arbitrable_Unit_OnlyPendingCouncil is Arbitrable_Unit_BaseTest { + modifier happyPath() { + _mockPendingCouncil(pendingCouncil); + + vm.startPrank(pendingCouncil); + _; + } + + function test_revertOnlyPendingCouncil() public { + vm.stopPrank(); + vm.expectRevert(IArbitrable.Arbitrable_OnlyPendingCouncil.selector); + arbitrable.mockOnlyPendingCouncil(); + } + + function test_onlyPendingCouncil() public happyPath { + arbitrable.mockOnlyPendingCouncil(); + } +} diff --git a/yarn.lock b/yarn.lock index c19cd49..f10f820 100644 --- a/yarn.lock +++ b/yarn.lock @@ -190,17 +190,22 @@ solc-typed-ast "18.1.2" yargs "17.7.2" -"@defi-wonderland/prophet-core-contracts@0.0.0-c25103ea": - version "0.0.0-c25103ea" - resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-c25103ea.tgz#e7a83271251d6ffbe0644c5971e6748d41ae9255" - integrity sha512-VCIeXwutXaih/ZF0JeqBjqkIs1ALWoFmn9OMeM5ZGcRZfx3JSV+axhi4YiIQVfrTLxICyu2hnbIEV7T1OmLVMQ== - -"@defi-wonderland/prophet-modules-contracts@0.0.0-ee716ee0": - version "0.0.0-ee716ee0" - resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-modules-contracts/-/prophet-modules-contracts-0.0.0-ee716ee0.tgz#cfcc221e8682f6a3cfe77a7353d7b2d894e198e7" - integrity sha512-wFb2FKmIgblXdz8fgTd5D0/ps6wzsnET6yXPhlD6yR2nW2x+/trXf3EvA5hrFzYnkfzp8gaHf60kDns4FAweSA== - dependencies: - "@defi-wonderland/prophet-core-contracts" "0.0.0-c25103ea" +"@defi-wonderland/prophet-core-contracts@0.0.0-438de1c5": + version "0.0.0-438de1c5" + resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-438de1c5.tgz#2b0238636c89eb71b07d57e12abfdea57c53b03b" + integrity sha512-UqdNRr2eH5DNSQ7qUriZbvR9LoXLXDMKTOjcjTTc8Gbq1aVcBhwFu4mSdrnhpQkXHjKtmsO0IfGcRHIGLvnXSQ== + +"@defi-wonderland/prophet-core-contracts@0.0.0-d01bc1a0": + version "0.0.0-d01bc1a0" + resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-core-contracts/-/prophet-core-contracts-0.0.0-d01bc1a0.tgz#ee4e8d970289a26966f6565b2f691d68d7b4232a" + integrity sha512-n4Dl1QgQAZafOtV7ef/fSoew2qlxWSdS389Z6PdIn7LxrHrzzRJi+nmJ1DJazMvMuhk/0dZbMhnaXMBi05E1zQ== + +"@defi-wonderland/prophet-modules-contracts@0.0.0-1197c328": + version "0.0.0-1197c328" + resolved "https://registry.yarnpkg.com/@defi-wonderland/prophet-modules-contracts/-/prophet-modules-contracts-0.0.0-1197c328.tgz#f58aa52a5a33fb39a9404bdf5e16ab75fcdde85f" + integrity sha512-78IRF5dSoHvVIzS9/NSqkuQrzPLrVKzRnBvOeSWt/vzoKy3Pm6rkcT5qzWZynIXbFNyMXGTa8HKYBWKFNlaE9A== + dependencies: + "@defi-wonderland/prophet-core-contracts" "0.0.0-d01bc1a0" "@openzeppelin/contracts" "4.9.5" solmate "https://github.com/transmissions11/solmate.git#bfc9c25865a274a7827fea5abf6e4fb64fc64e6c"