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

PBH multicall gas limit #113

Merged
merged 16 commits into from
Jan 7, 2025
Merged
5 changes: 4 additions & 1 deletion contracts/scripts/DeployDevnet.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@ contract DeployDevnet is Script {

uint8 constant TREE_DEPTH = 30;
uint256 constant INITIAL_ROOT = 0x918D46BF52D98B034413F4A1A1C41594E7A7A3F6AE08CB43D1A2A230E1959EF;
uint256 public constant MAX_PBH_GAS_LIMIT = 10000000;

address semaphoreVerifier = address(0);

address batchInsertionVerifiers = address(0);
address batchDeletionVerifiers = address(0);
uint256 public constant MAX_PBH_GAS_LIMIT = 10000000;


function run() public {
console.log(
Expand Down Expand Up @@ -77,7 +80,7 @@ contract DeployDevnet is Script {
pbhEntryPointImpl = address(new PBHEntryPointImplV1());
console.log("PBHEntryPointImplV1 Deployed at: ", pbhEntryPointImpl);
bytes memory initCallData = abi.encodeCall(
PBHEntryPointImplV1.initialize, (IWorldIDG(worldIdGroups), IEntryPoint(entryPoint), 30, address(0))
PBHEntryPointImplV1.initialize, (IWorldIDG(worldIdGroups), IEntryPoint(entryPoint), 30, address(0), MAX_PBH_GAS_LIMIT)
);
pbhEntryPoint = address(new PBHEntryPoint(pbhEntryPointImpl, initCallData));
console.log("PBHEntryPoint Deployed at: ", pbhEntryPoint);
Expand Down
57 changes: 49 additions & 8 deletions contracts/src/PBHEntryPointImplV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ contract PBHEntryPointImplV1 is IPBHEntryPoint, WorldIDImpl, ReentrancyGuard {
/// @notice Thrown when the hash of the user operations is invalid
error InvalidHashedOps();

/// @notice Thrown when the gas limit for a PBH multicall transaction is exceeded
error GasLimitExceeded(uint256 gasLimit);

/// @notice Thrown when setting the gas limit for a PBH multicall to 0
error InvalidPBHGasLimit(uint256 gasLimit);

///////////////////////////////////////////////////////////////////////////////
/// Events ///
//////////////////////////////////////////////////////////////////////////////
Expand All @@ -84,7 +90,11 @@ contract PBHEntryPointImplV1 is IPBHEntryPoint, WorldIDImpl, ReentrancyGuard {
/// @param numPbhPerMonth The number of allowed PBH transactions per month.
/// @param multicall3 Address of the Multicall3 implementation.
event PBHEntryPointImplInitialized(
IWorldID indexed worldId, IEntryPoint indexed entryPoint, uint8 indexed numPbhPerMonth, address multicall3
IWorldID indexed worldId,
IEntryPoint indexed entryPoint,
uint8 indexed numPbhPerMonth,
address multicall3,
uint256 pbhGasLimit
);

/// @notice Emitted once for each successful PBH verification.
Expand All @@ -104,6 +114,11 @@ contract PBHEntryPointImplV1 is IPBHEntryPoint, WorldIDImpl, ReentrancyGuard {
/// @param numPbhPerMonth The number of allowed PBH transactions per month.
event NumPbhPerMonthSet(uint8 indexed numPbhPerMonth);

/// @notice Emitted when setting the PBH gas limit.
///
/// @param pbhGasLimit The gas limit for a PBH multicall transaction.
event PBHGasLimitSet(uint256 indexed pbhGasLimit);

///////////////////////////////////////////////////////////////////////////////
/// Vars ///
//////////////////////////////////////////////////////////////////////////////
Expand All @@ -124,6 +139,9 @@ contract PBHEntryPointImplV1 is IPBHEntryPoint, WorldIDImpl, ReentrancyGuard {
/// @dev Whether a nullifier hash has been used already. Used to guarantee an action is only performed once by a single person
mapping(uint256 => bool) public nullifierHashes;

/// @notice The gas limit for a PBH multicall transaction
uint256 public pbhGasLimit;

///////////////////////////////////////////////////////////////////////////////
/// INITIALIZATION ///
///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -151,10 +169,13 @@ contract PBHEntryPointImplV1 is IPBHEntryPoint, WorldIDImpl, ReentrancyGuard {
/// @param _numPbhPerMonth The number of allowed PBH transactions per month.
///
/// @custom:reverts string If called more than once at the same initialisation number.
function initialize(IWorldID _worldId, IEntryPoint _entryPoint, uint8 _numPbhPerMonth, address _multicall3)
external
reinitializer(1)
{
function initialize(
IWorldID _worldId,
IEntryPoint _entryPoint,
uint8 _numPbhPerMonth,
address _multicall3,
uint256 _pbhGasLimit
) external reinitializer(1) {
if (address(_worldId) == address(0) || address(_entryPoint) == address(0) || _multicall3 == address(0)) {
revert AddressZero();
}
Expand All @@ -171,9 +192,14 @@ contract PBHEntryPointImplV1 is IPBHEntryPoint, WorldIDImpl, ReentrancyGuard {
numPbhPerMonth = _numPbhPerMonth;
multicall3 = _multicall3;

if (_pbhGasLimit == 0 || _pbhGasLimit > block.gaslimit) {
revert InvalidPBHGasLimit(_pbhGasLimit);
}

pbhGasLimit = _pbhGasLimit;
// Say that the contract is initialized.
__setInitialized();
emit PBHEntryPointImplInitialized(_worldId, _entryPoint, _numPbhPerMonth, _multicall3);
emit PBHEntryPointImplInitialized(_worldId, _entryPoint, _numPbhPerMonth, _multicall3, _pbhGasLimit);
}

/// @notice Responsible for initialising all of the supertypes of this contract.
Expand Down Expand Up @@ -275,13 +301,17 @@ contract PBHEntryPointImplV1 is IPBHEntryPoint, WorldIDImpl, ReentrancyGuard {
returns (IMulticall3.Result[] memory returnData)
{
uint256 signalHash = abi.encode(msg.sender, calls).hashToField();

verifyPbh(signalHash, pbhPayload);
nullifierHashes[pbhPayload.nullifierHash] = true;

returnData = IMulticall3(multicall3).aggregate3(calls);
returnData = IMulticall3(multicall3).aggregate3{gas: pbhGasLimit}(calls);
emit PBH(msg.sender, signalHash, pbhPayload);

// Check if pbh gas limit is exceeded
if (gasleft() > pbhGasLimit) {
revert GasLimitExceeded(gasleft());
}

return returnData;
}

Expand All @@ -307,4 +337,15 @@ contract PBHEntryPointImplV1 is IPBHEntryPoint, WorldIDImpl, ReentrancyGuard {
worldId = IWorldID(_worldId);
emit WorldIdSet(_worldId);
}

/// @notice Sets the max gas limit for a PBH multicall transaction.
/// @param _pbhGasLimit The max gas limit for a PBH multicall transaction.
function setPBHGasLimit(uint256 _pbhGasLimit) external virtual onlyProxy onlyInitialized onlyOwner {
karankurbur marked this conversation as resolved.
Show resolved Hide resolved
if (_pbhGasLimit == 0 || _pbhGasLimit > block.gaslimit) {
revert InvalidPBHGasLimit(_pbhGasLimit);
}

pbhGasLimit = _pbhGasLimit;
emit PBHGasLimitSet(_pbhGasLimit);
}
}
13 changes: 11 additions & 2 deletions contracts/src/interfaces/IPBHEntryPoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ interface IPBHEntryPoint {
external
returns (IMulticall3.Result[] memory returnData);

function initialize(IWorldID worldId, IEntryPoint entryPoint, uint8 _numPbhPerMonth, address _multicall3)
external;
function initialize(
IWorldID worldId,
IEntryPoint entryPoint,
uint8 _numPbhPerMonth,
address _multicall3,
uint256 _multicallGasLimit
) external;

function validateSignaturesCallback(bytes32 hashedOps) external view;

karankurbur marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -39,4 +44,8 @@ interface IPBHEntryPoint {
function setNumPbhPerMonth(uint8 _numPbhPerMonth) external;

function setWorldId(address _worldId) external;

function pbhGasLimit() external view returns (uint256);

function setPBHGasLimit(uint256 _pbhGasLimit) external;
}
46 changes: 42 additions & 4 deletions contracts/test/PBHEntryPointImplV1.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import {Safe4337Module} from "@4337/Safe4337Module.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@account-abstraction/contracts/interfaces/PackedUserOperation.sol";
import "@helpers/PBHExternalNullifier.sol";

/// @title PBHVerifer Verify Tests
/// @notice Contains tests for the pbhVerifier
/// @author Worldcoin

contract PBHEntryPointImplV1Test is TestSetup {
using ByteHasher for bytes;

Expand All @@ -46,7 +46,8 @@ contract PBHEntryPointImplV1Test is TestSetup {
IPBHEntryPoint.PBHPayload memory testPayload = TestUtils.mockPBHPayload(0, pbhNonce, extNullifier);

IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](1);
pbhEntryPoint.pbhMulticall(calls, testPayload);

pbhEntryPoint.pbhMulticall{gas: MAX_PBH_GAS_LIMIT}(calls, testPayload);

bytes memory testCallData = hex"c0ffee";
uint256 signalHash = abi.encodePacked(sender, pbhNonce, testCallData).hashToField();
Expand Down Expand Up @@ -158,7 +159,44 @@ contract PBHEntryPointImplV1Test is TestSetup {

vm.expectEmit(true, true, true, true);
emit PBH(address(this), signalHash, testPayload);
pbhEntryPoint.pbhMulticall(calls, testPayload);
pbhEntryPoint.pbhMulticall{gas: MAX_PBH_GAS_LIMIT}(calls, testPayload);
}

function test_pbhMulticall_RevertIf_GasLimitExceeded(uint8 pbhNonce) public {
vm.assume(pbhNonce < MAX_NUM_PBH_PER_MONTH);
address addr1 = address(0x1);
address addr2 = address(0x2);

uint256 extNullifier = TestUtils.getPBHExternalNullifier(pbhNonce);
IPBHEntryPoint.PBHPayload memory testPayload = TestUtils.mockPBHPayload(0, pbhNonce, extNullifier);

IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](2);

bytes memory testCallData = hex"";
calls[0] = IMulticall3.Call3({target: addr1, allowFailure: false, callData: testCallData});
calls[1] = IMulticall3.Call3({target: addr2, allowFailure: false, callData: testCallData});

// Catch the revert and check that it's a GasLimitExceeded with non-zero value
try pbhEntryPoint.pbhMulticall{gas: MAX_PBH_GAS_LIMIT * 2}(calls, testPayload) {
fail("Should have reverted with GasLimitExceeded");
} catch (bytes memory err) {
// Extract error selector
bytes4 selector = bytes4(err);
assertEq(selector, PBHEntryPointImplV1.GasLimitExceeded.selector);

// Extract value from error data and verify it's non-zero
uint256 gasLimit;
assembly {
gasLimit := mload(add(err, 36)) // 4 bytes selector + 32 bytes offset
}

assertTrue(
gasLimit < MAX_PBH_GAS_LIMIT * 2, "Error value for gasLeft should be less that what was provided"
);
assertTrue(
gasLimit > pbhEntryPoint.pbhGasLimit(), "Error value for gasLeft should be more than the pbhGasLimit"
);
}
}

function test_pbhMulticall_RevertIf_Reentrancy(uint8 pbhNonce) public {
Expand All @@ -172,7 +210,7 @@ contract PBHEntryPointImplV1Test is TestSetup {
bytes memory testCallData = abi.encodeWithSelector(IPBHEntryPoint.pbhMulticall.selector, calls, testPayload);
calls[0] = IMulticall3.Call3({target: address(pbhEntryPoint), allowFailure: true, callData: testCallData});

IMulticall3.Result memory returnData = pbhEntryPoint.pbhMulticall(calls, testPayload)[0];
IMulticall3.Result memory returnData = pbhEntryPoint.pbhMulticall{gas: MAX_PBH_GAS_LIMIT}(calls, testPayload)[0];

bytes memory expectedReturnData = abi.encodeWithSelector(ReentrancyGuard.ReentrancyGuardReentrantCall.selector);
assert(!returnData.success);
Expand Down
26 changes: 15 additions & 11 deletions contracts/test/PBHEntryPointImplV1Init.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {IWorldID} from "@world-id-contracts/interfaces/IWorldID.sol";
/// @author Worldcoin
contract PBHEntryPointImplV1InitTest is Test {
IPBHEntryPoint uninitializedPBHEntryPoint;
uint256 public constant MAX_PBH_GAS_LIMIT = 10000000;

function setUp() public {
address pbhEntryPointImpl = address(new PBHEntryPointImplV1());
Expand All @@ -32,10 +33,10 @@ contract PBHEntryPointImplV1InitTest is Test {

address pbhEntryPointImpl = address(new PBHEntryPointImplV1());
bytes memory initCallData =
abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, entryPoint, numPbh, multicall));
abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, entryPoint, numPbh, multicall, MAX_PBH_GAS_LIMIT));

vm.expectEmit(true, true, true, true);
emit PBHEntryPointImplV1.PBHEntryPointImplInitialized(worldId, entryPoint, numPbh, multicall);
emit PBHEntryPointImplV1.PBHEntryPointImplInitialized(worldId, entryPoint, numPbh, multicall, MAX_PBH_GAS_LIMIT);
IPBHEntryPoint(address(new PBHEntryPoint(pbhEntryPointImpl, initCallData)));
}

Expand All @@ -48,19 +49,22 @@ contract PBHEntryPointImplV1InitTest is Test {
address pbhEntryPointImpl = address(new PBHEntryPointImplV1());

// Expect revert when worldId is address(0)
bytes memory initCallData =
abi.encodeCall(PBHEntryPointImplV1.initialize, (IWorldID(address(0)), entryPoint, numPbh, multicall));
bytes memory initCallData = abi.encodeCall(
PBHEntryPointImplV1.initialize, (IWorldID(address(0)), entryPoint, numPbh, multicall, MAX_PBH_GAS_LIMIT)
);
vm.expectRevert(PBHEntryPointImplV1.AddressZero.selector);
IPBHEntryPoint(address(new PBHEntryPoint(pbhEntryPointImpl, initCallData)));

// Expect revert when entrypoint is address(0)
initCallData =
abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, IEntryPoint(address(0)), numPbh, multicall));
initCallData = abi.encodeCall(
PBHEntryPointImplV1.initialize, (worldId, IEntryPoint(address(0)), numPbh, multicall, MAX_PBH_GAS_LIMIT)
);
vm.expectRevert(PBHEntryPointImplV1.AddressZero.selector);
IPBHEntryPoint(address(new PBHEntryPoint(pbhEntryPointImpl, initCallData)));

// Expect revert when multicall3 is address(0)
initCallData = abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, entryPoint, numPbh, address(0)));
initCallData =
abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, entryPoint, numPbh, address(0), MAX_PBH_GAS_LIMIT));
vm.expectRevert(PBHEntryPointImplV1.AddressZero.selector);
IPBHEntryPoint(address(new PBHEntryPoint(pbhEntryPointImpl, initCallData)));
}
Expand All @@ -74,7 +78,7 @@ contract PBHEntryPointImplV1InitTest is Test {
address pbhEntryPointImpl = address(new PBHEntryPointImplV1());

bytes memory initCallData =
abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, entryPoint, numPbh, multicall));
abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, entryPoint, numPbh, multicall, MAX_PBH_GAS_LIMIT));
vm.expectRevert(PBHEntryPointImplV1.InvalidNumPbhPerMonth.selector);
IPBHEntryPoint(address(new PBHEntryPoint(pbhEntryPointImpl, initCallData)));
}
Expand All @@ -87,14 +91,14 @@ contract PBHEntryPointImplV1InitTest is Test {

address pbhEntryPointImpl = address(new PBHEntryPointImplV1());
bytes memory initCallData =
abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, entryPoint, numPbh, multicall));
abi.encodeCall(PBHEntryPointImplV1.initialize, (worldId, entryPoint, numPbh, multicall, MAX_PBH_GAS_LIMIT));

vm.expectEmit(true, true, true, true);
emit PBHEntryPointImplV1.PBHEntryPointImplInitialized(worldId, entryPoint, numPbh, multicall);
emit PBHEntryPointImplV1.PBHEntryPointImplInitialized(worldId, entryPoint, numPbh, multicall, MAX_PBH_GAS_LIMIT);
IPBHEntryPoint pbhEntryPoint = IPBHEntryPoint(address(new PBHEntryPoint(pbhEntryPointImpl, initCallData)));

vm.expectRevert("Initializable: contract is already initialized");
pbhEntryPoint.initialize(worldId, entryPoint, numPbh, multicall);
pbhEntryPoint.initialize(worldId, entryPoint, numPbh, multicall, MAX_PBH_GAS_LIMIT);
}

function test_verifyPbh_RevertIf_Uninitialized() public {
Expand Down
7 changes: 5 additions & 2 deletions contracts/test/TestSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ contract TestSetup is Test {
uint192 public constant PBH_NONCE_KEY = 1123123123;

uint8 public constant MAX_NUM_PBH_PER_MONTH = 30;
uint256 public constant MAX_PBH_GAS_LIMIT = 10000000;

///////////////////////////////////////////////////////////////////////////////
/// TEST ORCHESTRATION ///
///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -108,11 +110,12 @@ contract TestSetup is Test {
pbhEntryPointImpl = address(new PBHEntryPointImplV1());

bytes memory initCallData = abi.encodeCall(
PBHEntryPointImplV1.initialize, (initialGroupAddress, initialEntryPoint, MAX_NUM_PBH_PER_MONTH, MULTICALL3)
PBHEntryPointImplV1.initialize,
(initialGroupAddress, initialEntryPoint, MAX_NUM_PBH_PER_MONTH, MULTICALL3, MAX_PBH_GAS_LIMIT)
);
vm.expectEmit(true, true, true, true);
emit PBHEntryPointImplV1.PBHEntryPointImplInitialized(
initialGroupAddress, initialEntryPoint, MAX_NUM_PBH_PER_MONTH, MULTICALL3
initialGroupAddress, initialEntryPoint, MAX_NUM_PBH_PER_MONTH, MULTICALL3, MAX_PBH_GAS_LIMIT
);
pbhEntryPoint = IPBHEntryPoint(address(new PBHEntryPoint(pbhEntryPointImpl, initCallData)));
}
Expand Down
Loading