Skip to content

[BOOST-4538] feat(evm): lock down ownable auth handling on boostcore #36

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion packages/evm/contracts/BoostCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {AllowList} from "contracts/allowlists/AllowList.sol";
import {Budget} from "contracts/budgets/Budget.sol";
import {Incentive} from "contracts/incentives/Incentive.sol";
import {Validator} from "contracts/validators/Validator.sol";
import {IAuth} from "contracts/auth/IAuth.sol";

/// @title Boost Core
/// @notice The core contract for the Boost protocol
Expand Down Expand Up @@ -53,6 +54,8 @@ contract BoostCore is Ownable, ReentrancyGuard {
/// @notice The BoostRegistry contract
BoostRegistry public registry;

IAuth public createBoostAuth;

/// @notice The protocol fee receiver
address public protocolFeeReceiver;

Expand All @@ -68,6 +71,13 @@ contract BoostCore is Ownable, ReentrancyGuard {
/// @notice The fee denominator (basis points, i.e. 10000 == 100%)
uint64 public constant FEE_DENOMINATOR = 10_000;

modifier canCreateBoost(address sender) {
if (address(createBoostAuth) != address(0) && !createBoostAuth.isAuthorized(sender)) {
revert BoostError.Unauthorized();
}
_;
}

/// @notice Constructor to initialize the owner
constructor(BoostRegistry registry_, address protocolFeeReceiver_) {
_initializeOwner(msg.sender);
Expand All @@ -92,7 +102,12 @@ contract BoostCore is Ownable, ReentrancyGuard {
/// - `uint256` for the referralFee (added to the base referral fee)
/// - `uint256` for the maxParticipants
/// - `address` for the owner of the Boost
function createBoost(bytes calldata data_) external onlyOwner nonReentrant returns (BoostLib.Boost memory) {
function createBoost(bytes calldata data_)
external
canCreateBoost(msg.sender)
nonReentrant
returns (BoostLib.Boost memory)
{
InitPayload memory payload_ = abi.decode(data_.cdDecompress(), (InitPayload));

// Validate the Budget
Expand Down Expand Up @@ -160,6 +175,12 @@ contract BoostCore is Ownable, ReentrancyGuard {
return _boosts.length;
}

/// @notice Set the createBoostAuth address
/// @param auth_ The new createBoostAuth address
function setCreateBoostAuth(address auth_) external onlyOwner {
createBoostAuth = IAuth(auth_);
}

/// @notice Set the protocol fee receiver address
/// @param protocolFeeReceiver_ The new protocol fee receiver address
/// @dev This function is only callable by the owner
Expand Down
11 changes: 11 additions & 0 deletions packages/evm/contracts/auth/IAuth.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

/// @title IAuth Interface
/// @dev Interface for authorization contracts.
interface IAuth {
/// @notice Checks if an address is authorized
/// @param addr The address to check for authorization
/// @return bool Returns true if the address is authorized, false otherwise
function isAuthorized(address addr) external view returns (bool);
}
16 changes: 16 additions & 0 deletions packages/evm/contracts/auth/PassthroughAuth.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import {IAuth} from "contracts/auth/IAuth.sol";
/// @title Passthrough Authorization Contract
/// @dev Implements the IAuth interface, always authorizing access.

contract PassthroughAuth is IAuth {
/// @notice Checks if an address is authorized
/// @dev In this implementation, all addresses are authorized.
/// @param user The address to check for authorization
/// @return bool Always returns true, indicating any address is authorized
function isAuthorized(address user) public view override returns (bool) {
return true;
}
}
25 changes: 25 additions & 0 deletions packages/evm/contracts/shared/Mocks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {LibString} from "@solady/utils/LibString.sol";
import {ERC20} from "@solady/tokens/ERC20.sol";
import {ERC721} from "@solady/tokens/ERC721.sol";
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {IAuth} from "contracts/auth/IAuth.sol";

/**
* 🚨 WARNING: The mocks in this file are for testing purposes only. DO NOT use
Expand Down Expand Up @@ -72,3 +73,27 @@ contract MockERC1155 is ERC1155 {
_burn(from, id, amount);
}
}

/// @title Mock Authorization Contract
/// @dev Mock implementation of the IAuth interface for testing purposes.
/// Allows setting authorized addresses via the constructor.
contract MockAuth is IAuth {
mapping(address => bool) private _isAuthorized;

/// @notice Initializes the contract with a list of authorized addresses.
/// @param authorizedAddresses An array of addresses to be marked as authorized.
/// @dev Addresses not included in the list will default to unauthorized.
constructor(address[] memory authorizedAddresses) {
for (uint256 i = 0; i < authorizedAddresses.length; i++) {
_isAuthorized[authorizedAddresses[i]] = true;
}
}

/// @notice Checks if an address is authorized.
/// @param addr The address to check for authorization.
/// @return bool Returns true if the address is authorized, false otherwise.
/// @dev This function overrides the isAuthorized function in the IAuth interface.
function isAuthorized(address addr) external view override returns (bool) {
return _isAuthorized[addr];
}
}
42 changes: 41 additions & 1 deletion packages/evm/test/BoostCore.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.24;

import {Test, console} from "lib/forge-std/src/Test.sol";
import {MockERC20, MockERC721} from "contracts/shared/Mocks.sol";
import {MockERC20, MockERC721, MockAuth} from "contracts/shared/Mocks.sol";

import {LibClone} from "@solady/utils/LibClone.sol";
import {LibZip} from "@solady/utils/LibZip.sol";
Expand Down Expand Up @@ -41,6 +41,8 @@ contract BoostCoreTest is Test {

MockERC20 mockERC20 = new MockERC20();
MockERC721 mockERC721 = new MockERC721();
MockAuth mockAuth;
address[] mockAddresses;

BoostCore boostCore = new BoostCore(new BoostRegistry(), address(1));
BoostLib.Target action = _makeAction(address(mockERC721), MockERC721.mint.selector, mockERC721.mintPrice());
Expand Down Expand Up @@ -78,6 +80,8 @@ contract BoostCoreTest is Test {
})
)
);
mockAddresses.push(address(this));
mockAuth = new MockAuth(mockAddresses);
}

/////////////////////////////
Expand Down Expand Up @@ -242,6 +246,42 @@ contract BoostCoreTest is Test {
boostCore.createBoost(invalidActionCalldata);
}

//////////////////////////////////
// BoostCore.setCreateBoostAuth //
/////////////////////////////////

function testSetAuthToMockAuth() public {
// Assuming BoostCore has a function to set the auth strategy
boostCore.setCreateBoostAuth(address(mockAuth));
assertTrue(address(boostCore.createBoostAuth()) == address(mockAuth), "Auth strategy not set correctly");
}

function testAuthorizedUserCanCreateBoost() public {
// Set the auth strategy to MockAuth
boostCore.setCreateBoostAuth(address(mockAuth));

// Use an authorized address (this contract)
boostCore.createBoost(validCreateCalldata);

// Verify the boost was created
assertEq(1, boostCore.getBoostCount(), "Authorized user should be able to create boost");
}

function testUnauthorizedUserCannotCreateBoost() public {
// Set the auth strategy to MockAuth
boostCore.setCreateBoostAuth(address(mockAuth));

// Use an unauthorized address
vm.prank(makeAddr("unauthorizedBoostCreator"));

// Expect a revert due to unauthorized access
vm.expectRevert(BoostError.Unauthorized.selector);
boostCore.createBoost(validCreateCalldata);

// Verify no boost was created
assertEq(0, boostCore.getBoostCount(), "Unauthorized user should not be able to create boost");
}

///////////////////////////
// Test Helper Functions //
///////////////////////////
Expand Down
17 changes: 17 additions & 0 deletions packages/evm/test/shared/Mocks.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ pragma solidity ^0.8.24;

import {Test, console} from "lib/forge-std/src/Test.sol";
import {MockERC20, MockERC721} from "contracts/shared/Mocks.sol";
import {MockAuth} from "contracts/shared/Mocks.sol"; // Add this import at the top with the others

contract MocksTest is Test {
MockERC20 mockERC20;
MockERC721 mockERC721;
MockAuth mockAuth;
address[] mockAddresses;
address authorizedBoostCreator = makeAddr("authorizedBoostCreator");

function setUp() public {
mockERC20 = new MockERC20();
mockERC721 = new MockERC721();
mockAddresses.push(authorizedBoostCreator);
mockAuth = new MockAuth(mockAddresses);
}

///////////////
Expand Down Expand Up @@ -88,4 +94,15 @@ contract MocksTest is Test {
"https://example.com/token/115792089237316195423570985008687907853269984665640564039457584007913129639935"
);
}

//////////////
// MockAuth //
//////////////
function testMockAuthIsAuthorized() public {
assertTrue(mockAuth.isAuthorized(authorizedBoostCreator));
}

function testMockAuthIsNotAuthorized() public {
assertFalse(mockAuth.isAuthorized(address(this)));
}
}
Loading