Skip to content

Commit

Permalink
feat: add access control for authorized callers (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
xorsal authored Sep 25, 2024
1 parent 6986fee commit 7dd25d3
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 24 deletions.
21 changes: 17 additions & 4 deletions solidity/contracts/extensions/BondEscalationAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,20 @@ 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) {
uint256 _length = _authorizedCallers.length;
for (uint256 _i; _i < _length; ++_i) {
authorizedCallers[_authorizedCallers[_i]] = true;
}
}

modifier onlyAuthorizedCaller() {
if (!authorizedCallers[msg.sender]) revert BondEscalationAccounting_UnauthorizedCaller();
_;
}

/// @inheritdoc IBondEscalationAccounting
function pledge(
Expand All @@ -28,7 +41,7 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
IOracle.Dispute calldata _dispute,
IERC20 _token,
uint256 _amount
) external {
) external onlyAuthorizedCaller {
bytes32 _requestId = _getId(_request);
bytes32 _disputeId = _validateDispute(_request, _dispute);

Expand All @@ -51,7 +64,7 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
IERC20 _token,
uint256 _amountPerPledger,
uint256 _winningPledgersLength
) external {
) external onlyAuthorizedCaller {
bytes32 _requestId = _getId(_request);
bytes32 _disputeId = _validateDispute(_request, _dispute);

Expand Down Expand Up @@ -127,7 +140,7 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
address _pledger,
IERC20 _token,
uint256 _amount
) external {
) external onlyAuthorizedCaller {
bytes32 _requestId = _getId(_request);
bytes32 _disputeId = _validateDispute(_request, _dispute);

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);
console.log('BOND_ESCALATION_ACCOUNTING_EXTENSION:', address(bondEscalationAccounting));

// Deploy circuit resolver module
Expand Down
73 changes: 69 additions & 4 deletions solidity/test/integration/BondEscalation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ contract Integration_BondEscalation is IntegrationBase {
address internal _secondDisputer = makeAddr('secondDisputer');
address internal _secondProposer = makeAddr('secondProposer');
address internal _thirdProposer = makeAddr('thirdProposer');
address internal _attacker = makeAddr('attacker');

bytes internal _responseData = abi.encode('response');

Expand All @@ -25,6 +26,11 @@ contract Integration_BondEscalation is IntegrationBase {
_expectedDeadline = block.timestamp + 10 days;
_bondEscalationDeadline = block.timestamp + 5 days;

setUpRequest();
setUpEscalation();
}

function setUpRequest() public {
mockRequest.requestModuleData = abi.encode(
IHttpRequestModule.RequestParameters({
url: _expectedUrl,
Expand Down Expand Up @@ -59,9 +65,12 @@ contract Integration_BondEscalation is IntegrationBase {
);

mockRequest.disputeModule = address(_bondEscalationModule);
mockRequest.nonce = uint96(oracle.totalRequestCount());

_resetMockIds();
}

function setUpEscalation() public {
// Set up all approvals
vm.prank(requester);
_bondEscalationAccounting.approveModule(address(_requestModule));
Expand Down Expand Up @@ -470,8 +479,6 @@ contract Integration_BondEscalation is IntegrationBase {

////////////////// NEW MALICIOUS REQUEST ////////////////////////

address _attacker = makeAddr('attacker');

mockRequest.nonce += 1;
mockRequest.requester = _attacker;
mockRequest.disputeModule = _attacker;
Expand All @@ -486,8 +493,66 @@ contract Integration_BondEscalation is IntegrationBase {
})
);

uint256 _attackerBalance = _bondEscalationAccounting.balanceOf(_attacker, usdc);
assertEq(_attackerBalance, 0);
vm.startPrank(_attacker);
// Create a new proposal with another dispute module
_bondEscalationAccounting.approveModule(mockRequest.requestModule);

vm.expectRevert(IBondEscalationAccounting.BondEscalationAccounting_UnauthorizedCaller.selector);
_bondEscalationAccounting.releasePledge(mockRequest, mockDispute, _attacker, usdc, _pledgeSize * 4);
vm.stopPrank();
}

function test_authorizedAttackerAllowedModules() public {
// redeploy BondEscalationAccounting authorizing the attacker
address[] memory _authorizedCallers = new address[](3);
_authorizedCallers[0] = address(_bondEscalationModule);
_authorizedCallers[1] = _attacker;
_bondEscalationAccounting = new BondEscalationAccounting(oracle, _authorizedCallers);

label(address(_bondEscalationAccounting), 'BondEscalationModule');
setUpRequest();
setUpEscalation();

////////////////// DISPUTE ESCALATION ////////////////////////
// Step 1: Proposer pledges against the dispute
_deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize);
vm.prank(proposer);
_bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute);

// Step 2: Disputer doubles down
_deposit(_bondEscalationAccounting, disputer, usdc, _pledgeSize);
vm.prank(disputer);
_bondEscalationModule.pledgeForDispute(mockRequest, mockDispute);

// Step 3: Proposer doubles down
_deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize);
vm.prank(proposer);
_bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute);

// Step 4: Disputer runs out of capital
// Step 5: The tying buffer kicks in
vm.warp(_bondEscalationDeadline + 1);

// Step 6: An external party sees that Proposer's response is incorrect, so they bond the required WETH
_deposit(_bondEscalationAccounting, _secondDisputer, usdc, _pledgeSize);
vm.prank(_secondDisputer);
_bondEscalationModule.pledgeForDispute(mockRequest, mockDispute);

////////////////// NEW MALICIOUS REQUEST ////////////////////////

mockRequest.nonce += 1;
mockRequest.requester = _attacker;
mockRequest.disputeModule = _attacker;
mockRequest.requestModuleData = abi.encode(
IHttpRequestModule.RequestParameters({
url: _expectedUrl,
body: _expectedBody,
method: _expectedMethod,
accountingExtension: _bondEscalationAccounting,
paymentToken: usdc,
paymentAmount: 0
})
);

vm.startPrank(_attacker);
// Create a new proposal with another dispute 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 @@ contract IntegrationBase is DSTestPlus, TestConstants, Helpers {
_bondEscalationModule = new BondEscalationModule(oracle);
label(address(_bondEscalationModule), 'BondEscalationModule');

_bondEscalationAccounting = new BondEscalationAccounting(oracle);
address[] memory _authorizedCallers = new address[](1);
_authorizedCallers[0] = address(_bondEscalationModule);
_bondEscalationAccounting = new BondEscalationAccounting(oracle, _authorizedCallers);
label(address(_bondEscalationAccounting), 'BondEscalationAccounting');

_mockCallback = new MockCallback();
Expand Down
Loading

0 comments on commit 7dd25d3

Please sign in to comment.