Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add access control for authorized callers #62

Merged
merged 13 commits into from
Sep 25, 2024
13 changes: 12 additions & 1 deletion solidity/contracts/extensions/BondEscalationAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
/// @inheritdoc IBondEscalationAccounting
mapping(bytes32 _requestId => mapping(address _pledger => bool _claimed)) public pledgerClaimed;

constructor(IOracle _oracle) AccountingExtension(_oracle) {}
/// @inheritdoc IBondEscalationAccounting
mapping(address _caller => bool _authorized) public authorizedCallers;

constructor(IOracle _oracle, address[] memory _authorizedCallers) AccountingExtension(_oracle) {
xorsal marked this conversation as resolved.
Show resolved Hide resolved
uint256 _length = _authorizedCallers.length;
for (uint256 _i; _i < _length; ++_i) {
authorizedCallers[_authorizedCallers[_i]] = true;
}
}

/// @inheritdoc IBondEscalationAccounting
function pledge(
Expand All @@ -32,6 +40,7 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
bytes32 _requestId = _getId(_request);
bytes32 _disputeId = _validateDispute(_request, _dispute);

if (!authorizedCallers[msg.sender]) revert BondEscalationAccounting_UnauthorizedCaller();
if (!ORACLE.allowedModule(_requestId, msg.sender)) revert AccountingExtension_UnauthorizedModule();
if (balanceOf[_pledger][_token] < _amount) revert BondEscalationAccounting_InsufficientFunds();

Expand All @@ -55,6 +64,7 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
bytes32 _requestId = _getId(_request);
bytes32 _disputeId = _validateDispute(_request, _dispute);

if (!authorizedCallers[msg.sender]) revert BondEscalationAccounting_UnauthorizedCaller();
if (!ORACLE.allowedModule(_requestId, msg.sender)) revert AccountingExtension_UnauthorizedModule();

if (pledges[_disputeId][_token] < _amountPerPledger * _winningPledgersLength) {
Expand Down Expand Up @@ -131,6 +141,7 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
bytes32 _requestId = _getId(_request);
bytes32 _disputeId = _validateDispute(_request, _dispute);

if (!authorizedCallers[msg.sender]) revert BondEscalationAccounting_UnauthorizedCaller();
if (!ORACLE.allowedModule(_requestId, msg.sender)) revert AccountingExtension_UnauthorizedModule();
xorsal marked this conversation as resolved.
Show resolved Hide resolved

if (pledges[_disputeId][_token] < _amount) revert BondEscalationAccounting_InsufficientFunds();
Expand Down
13 changes: 13 additions & 0 deletions solidity/interfaces/extensions/IBondEscalationAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ interface IBondEscalationAccounting is IAccountingExtension {
*/
error BondEscalationAccounting_AlreadySettled();

/**
* @notice Thrown when caller is not authorized
*/
error BondEscalationAccounting_UnauthorizedCaller();

/*///////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -162,6 +167,14 @@ interface IBondEscalationAccounting is IAccountingExtension {
*/
function pledgerClaimed(bytes32 _requestId, address _pledger) external returns (bool _claimed);

/**
* @notice Checks whether an address is an authorized caller.
*
* @param _caller The address to check
* @return _authorized True if the address is authorized, false otherwise
*/
function authorizedCallers(address _caller) external returns (bool _authorized);

/*///////////////////////////////////////////////////////////////
LOGIC
//////////////////////////////////////////////////////////////*/
Expand Down
4 changes: 3 additions & 1 deletion solidity/scripts/Deploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ contract Deploy is Script {
console.log('ACCOUNTING_EXTENSION:', address(accountingExtension));

// Deploy bond escalation accounting
bondEscalationAccounting = new BondEscalationAccounting(oracle);
address[] memory authorizedCallers = new address[](1);
authorizedCallers[0] = address(bondEscalationModule);
bondEscalationAccounting = new BondEscalationAccounting(oracle, authorizedCallers);
Comment on lines +128 to +130
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note here that we don't have any integration tests for the BondEscalationResolutionModule, only for the dispute one. Hence we're not adding it to the list of authorized callers.

console.log('BOND_ESCALATION_ACCOUNTING_EXTENSION:', address(bondEscalationAccounting));

// Deploy circuit resolver module
Expand Down
4 changes: 3 additions & 1 deletion solidity/test/integration/IntegrationBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@
_bondEscalationModule = new BondEscalationModule(oracle);
label(address(_bondEscalationModule), 'BondEscalationModule');

_bondEscalationAccounting = new BondEscalationAccounting(oracle);
address[] memory authorizedCallers = new address[](1);

Check warning on line 128 in solidity/test/integration/IntegrationBase.sol

View workflow job for this annotation

GitHub Actions / Run Linters (16.x)

'authorizedCallers' should start with _
xorsal marked this conversation as resolved.
Show resolved Hide resolved
authorizedCallers[0] = address(_bondEscalationModule);
_bondEscalationAccounting = new BondEscalationAccounting(oracle, authorizedCallers);
label(address(_bondEscalationAccounting), 'BondEscalationAccounting');

_mockCallback = new MockCallback();
Expand Down
103 changes: 89 additions & 14 deletions solidity/test/unit/modules/dispute/BondEscalationAccounting.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import {IAccountingExtension} from '../../../../interfaces/extensions/IAccountin
import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';

contract ForTest_BondEscalationAccounting is BondEscalationAccounting {
constructor(IOracle _oracle) BondEscalationAccounting(_oracle) {}
constructor(
IOracle _oracle,
address[] memory _authorizedModules
) BondEscalationAccounting(_oracle, _authorizedModules) {}
xorsal marked this conversation as resolved.
Show resolved Hide resolved

function forTest_setPledge(bytes32 _disputeId, IERC20 _token, uint256 _amount) public {
pledges[_disputeId][_token] = _amount;
Expand Down Expand Up @@ -65,6 +68,9 @@ contract BaseTest is Test, Helpers {
address public bonder = makeAddr('bonder');
address public pledger = makeAddr('pledger');

address public authorizedCaller = makeAddr('authorizedCaller');
address public unauthorizedCaller = makeAddr('unauthorizedCaller');

// Pledged Event
event Pledged(
address indexed _pledger, bytes32 indexed _requestId, bytes32 indexed _disputeId, IERC20 _token, uint256 _amount
Expand Down Expand Up @@ -105,22 +111,43 @@ contract BaseTest is Test, Helpers {
token = IERC20(makeAddr('ERC20'));
vm.etch(address(token), hex'069420');

bondEscalationAccounting = new ForTest_BondEscalationAccounting(oracle);
address[] memory _authorizedCallers = new address[](1);
_authorizedCallers[0] = authorizedCaller;
bondEscalationAccounting = new ForTest_BondEscalationAccounting(oracle, _authorizedCallers);
}
}

contract BondEscalationAccounting_Unit_Pledge is BaseTest {
function test_revertIfCallerIsUnauthorized(address _pledger, uint256 _amount) public {
vm.assume(_amount > 0);
xorsal marked this conversation as resolved.
Show resolved Hide resolved

(, IOracle.Dispute memory _dispute) = _getResponseAndDispute(oracle);

// Check: does it revert if the caller is not authorized?
vm.expectRevert(IBondEscalationAccounting.BondEscalationAccounting_UnauthorizedCaller.selector);

vm.prank(unauthorizedCaller);
bondEscalationAccounting.pledge({
_pledger: _pledger,
_request: mockRequest,
_dispute: _dispute,
_token: token,
_amount: _amount
});
}

function test_revertIfDisallowedModule(address _pledger, uint256 _amount) public {
(, IOracle.Dispute memory _dispute) = _getResponseAndDispute(oracle);

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_getId(mockRequest), address(this))), abi.encode(false)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_getId(mockRequest), authorizedCaller)), abi.encode(false)
);

// Check: does it revert if called by an unauthorized module?
vm.expectRevert(IAccountingExtension.AccountingExtension_UnauthorizedModule.selector);

vm.prank(authorizedCaller);
bondEscalationAccounting.pledge({
_pledger: _pledger,
_request: mockRequest,
Expand All @@ -137,12 +164,13 @@ contract BondEscalationAccounting_Unit_Pledge is BaseTest {
bytes32 _requestId = _getId(mockRequest);

_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(true)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(true)
);

// Check: does it revert if the pledger doesn't have enough deposited?
vm.expectRevert(IBondEscalationAccounting.BondEscalationAccounting_InsufficientFunds.selector);

vm.prank(authorizedCaller);
bondEscalationAccounting.pledge({
_pledger: _pledger,
_request: mockRequest,
Expand All @@ -159,7 +187,7 @@ contract BondEscalationAccounting_Unit_Pledge is BaseTest {

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(true)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(true)
);

bondEscalationAccounting.forTest_setBalanceOf(_pledger, token, _amount);
Expand All @@ -171,6 +199,7 @@ contract BondEscalationAccounting_Unit_Pledge is BaseTest {
uint256 _balanceBeforePledge = bondEscalationAccounting.balanceOf(_pledger, token);
uint256 _pledgesBeforePledge = bondEscalationAccounting.pledges(_disputeId, token);

vm.prank(authorizedCaller);
bondEscalationAccounting.pledge({
_pledger: _pledger,
_request: mockRequest,
Expand All @@ -190,18 +219,39 @@ contract BondEscalationAccounting_Unit_Pledge is BaseTest {
}

contract BondEscalationAccounting_Unit_OnSettleBondEscalation is BaseTest {
function test_revertIfCallerIsUnauthorized(uint256 _amountPerPledger, uint256 _numOfWinningPledgers) public {
(, IOracle.Dispute memory _dispute) = _getResponseAndDispute(oracle);

// Note, bounding to a max of 30 so that the tests doesn't take forever to run
_numOfWinningPledgers = bound(_numOfWinningPledgers, 1, 30);
_amountPerPledger = bound(_amountPerPledger, 1, type(uint256).max / _numOfWinningPledgers);
xorsal marked this conversation as resolved.
Show resolved Hide resolved

// Check: does it revert if the caller is not authorized?
vm.expectRevert(IBondEscalationAccounting.BondEscalationAccounting_UnauthorizedCaller.selector);

vm.prank(unauthorizedCaller);
bondEscalationAccounting.onSettleBondEscalation({
_request: mockRequest,
_dispute: _dispute,
_token: token,
_amountPerPledger: _amountPerPledger,
_winningPledgersLength: _numOfWinningPledgers
});
}

function test_revertIfDisallowedModule(uint256 _numOfWinningPledgers, uint256 _amountPerPledger) public {
(, IOracle.Dispute memory _dispute) = _getResponseAndDispute(oracle);
bytes32 _requestId = _getId(mockRequest);

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(false)
);

// Check: does it revert if the module is not allowed?
vm.expectRevert(IAccountingExtension.AccountingExtension_UnauthorizedModule.selector);

vm.prank(authorizedCaller);
bondEscalationAccounting.onSettleBondEscalation({
_request: mockRequest,
_dispute: _dispute,
Expand All @@ -221,7 +271,7 @@ contract BondEscalationAccounting_Unit_OnSettleBondEscalation is BaseTest {

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(true)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(true)
);

// Mock and expect the call to oracle checking if the dispute exists
Expand All @@ -236,6 +286,7 @@ contract BondEscalationAccounting_Unit_OnSettleBondEscalation is BaseTest {
// Check: does it revert if the escalation is already settled?
vm.expectRevert(IBondEscalationAccounting.BondEscalationAccounting_AlreadySettled.selector);

vm.prank(authorizedCaller);
bondEscalationAccounting.onSettleBondEscalation({
_request: mockRequest,
_dispute: mockDispute,
Expand All @@ -256,7 +307,7 @@ contract BondEscalationAccounting_Unit_OnSettleBondEscalation is BaseTest {

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(true)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(true)
);

address[] memory _winningPledgers = _createWinningPledgersArray(_numOfWinningPledgers);
Expand All @@ -269,6 +320,7 @@ contract BondEscalationAccounting_Unit_OnSettleBondEscalation is BaseTest {
// Check: does it revert if the pledger does not have enough funds?
vm.expectRevert(IBondEscalationAccounting.BondEscalationAccounting_InsufficientFunds.selector);

vm.prank(authorizedCaller);
bondEscalationAccounting.onSettleBondEscalation({
_request: mockRequest,
_dispute: _dispute,
Expand All @@ -289,7 +341,7 @@ contract BondEscalationAccounting_Unit_OnSettleBondEscalation is BaseTest {

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(true)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(true)
);

address[] memory _winningPledgers = _createWinningPledgersArray(_numOfWinningPledgers);
Expand All @@ -301,6 +353,7 @@ contract BondEscalationAccounting_Unit_OnSettleBondEscalation is BaseTest {
vm.expectEmit(true, true, true, true, address(bondEscalationAccounting));
emit BondEscalationSettled(_requestId, _disputeId, token, _amountPerPledger, _numOfWinningPledgers);

vm.prank(authorizedCaller);
bondEscalationAccounting.onSettleBondEscalation({
_request: mockRequest,
_dispute: _dispute,
Expand All @@ -320,23 +373,42 @@ contract BondEscalationAccounting_Unit_OnSettleBondEscalation is BaseTest {
assertEq(_requestIdSaved, _requestId);
assertEq(address(_token), address(token));
assertEq(_amountPerPledgerSaved, _amountPerPledger);
assertEq(address(_bondEscalationModule), address(this));
assertEq(address(_bondEscalationModule), authorizedCaller);
}
}

contract BondEscalationAccounting_Unit_ReleasePledge is BaseTest {
function test_revertIfCallerIsUnauthorized(uint256 _amount, address _pledger) public {
vm.assume(_amount < type(uint256).max);
xorsal marked this conversation as resolved.
Show resolved Hide resolved

(, IOracle.Dispute memory _dispute) = _getResponseAndDispute(oracle);

// Check: does it revert if the caller is not authorized?
vm.expectRevert(IBondEscalationAccounting.BondEscalationAccounting_UnauthorizedCaller.selector);

vm.prank(unauthorizedCaller);
bondEscalationAccounting.releasePledge({
_request: mockRequest,
_dispute: _dispute,
_pledger: _pledger,
_token: token,
_amount: _amount
});
}

function test_revertIfDisallowedModule(address _pledger, uint256 _amount) public {
(, IOracle.Dispute memory _dispute) = _getResponseAndDispute(oracle);
bytes32 _requestId = _getId(mockRequest);

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(false)
);

// Check: does it revert if the module is not authorized?
vm.expectRevert(IAccountingExtension.AccountingExtension_UnauthorizedModule.selector);

vm.prank(authorizedCaller);
bondEscalationAccounting.releasePledge({
_request: mockRequest,
_dispute: _dispute,
Expand All @@ -355,7 +427,7 @@ contract BondEscalationAccounting_Unit_ReleasePledge is BaseTest {

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(true)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(true)
);

bondEscalationAccounting.forTest_setPledge(_disputeId, token, _amount);
Expand All @@ -364,6 +436,7 @@ contract BondEscalationAccounting_Unit_ReleasePledge is BaseTest {
// Check: does it revert if the pledger does not have enough funds pledged?
vm.expectRevert(IBondEscalationAccounting.BondEscalationAccounting_InsufficientFunds.selector);

vm.prank(authorizedCaller);
bondEscalationAccounting.releasePledge({
_request: mockRequest,
_dispute: _dispute,
Expand All @@ -380,11 +453,12 @@ contract BondEscalationAccounting_Unit_ReleasePledge is BaseTest {

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(true)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(true)
);

bondEscalationAccounting.forTest_setPledge(_disputeId, token, _amount);

vm.prank(authorizedCaller);
bondEscalationAccounting.releasePledge({
_request: mockRequest,
_dispute: _dispute,
Expand All @@ -404,7 +478,7 @@ contract BondEscalationAccounting_Unit_ReleasePledge is BaseTest {

// Mock and expect the call to oracle checking if the module is allowed
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(true)
address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, authorizedCaller)), abi.encode(true)
);

bondEscalationAccounting.forTest_setPledge(_disputeId, token, _amount);
Expand All @@ -413,6 +487,7 @@ contract BondEscalationAccounting_Unit_ReleasePledge is BaseTest {
vm.expectEmit(true, true, true, true, address(bondEscalationAccounting));
emit PledgeReleased(_requestId, _disputeId, _pledger, token, _amount);

vm.prank(authorizedCaller);
bondEscalationAccounting.releasePledge({
_request: mockRequest,
_dispute: _dispute,
Expand Down
Loading