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

fix:tests #28

Merged
merged 7 commits into from
Oct 17, 2024
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@
"test": "pnpm run test:forge",
"test:gas:forge": "forge test --gas-report",
"test:gas": "pnpm test:gas:forge",
"coverage:forge": "forge coverage --ir-minimum",
"coverage:forge": "forge coverage --ir-minimum --gas-limit 50000000 --gas-price 1000000000",
"coverage": "pnpm run coverage:forge",
"coverage:report": "forge coverage --ir-minimum --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage/foundry && mv lcov.info coverage/foundry",
"coverage:report": "forge coverage --ir-minimum --gas-limit 50000000 --gas-price 1000000000 --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage/foundry && mv lcov.info coverage/foundry",
"deploy:forge": "forge script scripts/solidity/Deploy.s.sol --broadcast --rpc-url http://localhost:8545",
"lint:sol": "pnpm solhint 'contracts/**/*.sol'",
"format:check": "forge fmt --check",
Expand Down
151 changes: 73 additions & 78 deletions test/base/TestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
Vm.Wallet internal PAYMASTER_FEE_COLLECTOR;
Vm.Wallet internal DAPP_ACCOUNT;

struct PaymasterData {
uint128 validationGasLimit;
uint128 postOpGasLimit;
address paymasterId;
uint48 validUntil;
uint48 validAfter;
uint32 priceMarkup;
}

// Used to buffer user op gas limits
// GAS_LIMIT = (ESTIMATED_GAS * GAS_BUFFER_RATIO) / 100
uint8 private constant GAS_BUFFER_RATIO = 110;
Expand Down Expand Up @@ -80,11 +89,11 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOp);
verificationGasUsed = gasleft();
IAccount(userOp.sender).validateUserOp(userOp, userOpHash, 0);
verificationGasUsed = verificationGasUsed - gasleft();
verificationGasUsed = verificationGasUsed - gasleft(); //+ 21000;

callGasUsed = gasleft();
bool success = Exec.call(userOp.sender, 0, userOp.callData, 3e6);
callGasUsed = callGasUsed - gasleft();
callGasUsed = callGasUsed - gasleft(); //+ 21000;
assert(success);

verificationGasLimit = (verificationGasUsed * GAS_BUFFER_RATIO) / 100;
Expand All @@ -108,68 +117,65 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
// Estimate gas used
validationGasUsed = gasleft();
(context,) = paymaster.validatePaymasterUserOp(userOp, userOpHash, requiredPreFund);
validationGasUsed = validationGasUsed - gasleft();
validationGasUsed = validationGasUsed - gasleft(); //+ 21000;

postopGasUsed = gasleft();
paymaster.postOp(IPaymaster.PostOpMode.opSucceeded, context, 1e12, 3e6);
postopGasUsed = (postopGasUsed - gasleft());
postopGasUsed = (postopGasUsed - gasleft()); //+ 21000;

validationGasLimit = (validationGasUsed * GAS_BUFFER_RATIO) / 100;
postopGasLimit = (postopGasUsed * GAS_BUFFER_RATIO) / 100;
}

// Note: we can use externally provided gas limits to override.
// Note: we can pass callData and callGasLimit as args to test with more tx types
// Note: we can pass Nexus instance instead of sender EOA and comuting counterfactual within buildUserOpWithCalldata

function createUserOp(
Vm.Wallet memory sender,
BiconomySponsorshipPaymaster paymaster,
uint32 priceMarkup
uint32 priceMarkup,
uint128 postOpGasLimitOverride
)
internal
returns (PackedUserOperation memory userOp, bytes32 userOpHash)
{
// Create userOp with no gas estimates
uint48 validUntil = uint48(block.timestamp + 1 days);
uint48 validAfter = uint48(block.timestamp);

userOp = buildUserOpWithCalldata(sender, "", address(VALIDATOR_MODULE));

(userOp.paymasterAndData,) = generateAndSignPaymasterData(
userOp, PAYMASTER_SIGNER, paymaster, 3e6, 8e3, DAPP_ACCOUNT.addr, validUntil, validAfter, priceMarkup
);
PaymasterData memory pmData = PaymasterData({
validationGasLimit: 100_000,
postOpGasLimit: uint128(postOpGasLimitOverride),
paymasterId: DAPP_ACCOUNT.addr,
validUntil: uint48(block.timestamp + 1 days),
validAfter: uint48(block.timestamp),
priceMarkup: priceMarkup
});
(userOp.paymasterAndData,) = generateAndSignPaymasterData(userOp, PAYMASTER_SIGNER, paymaster, pmData);
userOp.signature = signUserOp(sender, userOp);

(,, uint256 verificationGasLimit, uint256 callGasLimit) = estimateUserOpGasCosts(userOp);
// Estimate paymaster gas limits
(, uint256 postopGasUsed, uint256 validationGasLimit, uint256 postopGasLimit) =
estimatePaymasterGasCosts(paymaster, userOp, 5e4);

// console2.log("postOpGasUsed");
// console2.logUint(postopGasUsed);

// uint256 prevValUnaccountedGas = paymaster.unaccountedGas();
// console2.logUint(prevValUnaccountedGas);

// Below may not be needed if unaccountedGas is set correctly
vm.startPrank(paymaster.owner());
// Set unaccounted gas to be gas used in postop + 1000 for EP overhead and penalty
paymaster.setUnaccountedGas(postopGasUsed + 500);
vm.stopPrank();

// uint256 currentValUnaccountedGas = paymaster.unaccountedGas();
// console2.logUint(currentValUnaccountedGas);

// Ammend the userop to have new gas limits and signature
userOp.accountGasLimits = bytes32(abi.encodePacked(uint128(verificationGasLimit), uint128(callGasLimit)));
(userOp.paymasterAndData,) = generateAndSignPaymasterData(
userOp,
PAYMASTER_SIGNER,
paymaster,
uint128(validationGasLimit),
uint128(postopGasLimit),
// Estimate account gas limits
// (,, uint256 verificationGasLimit, uint256 callGasLimit) = estimateUserOpGasCosts(userOp);
// // Estimate paymaster gas limits
// (, uint256 postopGasUsed, uint256 validationGasLimit, uint256 postopGasLimit) =
// estimatePaymasterGasCosts(paymaster, userOp, 5e4);

// vm.startPrank(paymaster.owner());
// paymaster.setUnaccountedGas(postopGasUsed + 500);
// vm.stopPrank();

// Ammend the userop to have updated / overridden gas limits
userOp.accountGasLimits = bytes32(abi.encodePacked(uint128(100_000), uint128(0)));
PaymasterData memory pmDataNew = PaymasterData(
uint128(100_000),
uint128(postOpGasLimitOverride),
DAPP_ACCOUNT.addr,
validUntil,
validAfter,
uint48(block.timestamp + 1 days),
uint48(block.timestamp),
priceMarkup
);

(userOp.paymasterAndData,) = generateAndSignPaymasterData(userOp, PAYMASTER_SIGNER, paymaster, pmDataNew);
userOp.signature = signUserOp(sender, userOp);
userOpHash = ENTRYPOINT.getUserOpHash(userOp);
}
Expand All @@ -185,52 +191,48 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
PackedUserOperation memory userOp,
Vm.Wallet memory signer,
BiconomySponsorshipPaymaster paymaster,
uint128 paymasterValGasLimit,
uint128 paymasterPostOpGasLimit,
address paymasterId,
uint48 validUntil,
uint48 validAfter,
uint32 priceMarkup
PaymasterData memory pmData
)
internal
view
returns (bytes memory finalPmData, bytes memory signature)
{
// Initial paymaster data with zero signature
bytes memory initialPmData = abi.encodePacked(
userOp.paymasterAndData = abi.encodePacked(
address(paymaster),
paymasterValGasLimit,
paymasterPostOpGasLimit,
paymasterId,
validUntil,
validAfter,
priceMarkup,
pmData.validationGasLimit,
pmData.postOpGasLimit,
pmData.paymasterId,
pmData.validUntil,
pmData.validAfter,
pmData.priceMarkup,
new bytes(65) // Zero signature
);

// Update user operation with initial paymaster data
userOp.paymasterAndData = initialPmData;


{
// Generate hash to be signed
bytes32 paymasterHash = paymaster.getHash(userOp, paymasterId, validUntil, validAfter, priceMarkup);
bytes32 paymasterHash =
paymaster.getHash(userOp, pmData.paymasterId, pmData.validUntil, pmData.validAfter, pmData.priceMarkup);

// Sign the hash
signature = signMessage(signer, paymasterHash);
require(signature.length == 65, "Invalid Paymaster Signature length");
}

// Final paymaster data with the actual signature
finalPmData = abi.encodePacked(
address(paymaster),
paymasterValGasLimit,
paymasterPostOpGasLimit,
paymasterId,
validUntil,
validAfter,
priceMarkup,
pmData.validationGasLimit,
pmData.postOpGasLimit,
pmData.paymasterId,
pmData.validUntil,
pmData.validAfter,
pmData.priceMarkup,
signature
);
}

// Note: Token paymaster could also get into stack deep issues.
// TODO: Refactor to reduce stack depth
/// @notice Generates and signs the paymaster data for a user operation.
/// @dev This function prepares the `paymasterAndData` field for a `PackedUserOperation` with the correct signature.
/// @param userOp The user operation to be signed.
Expand Down Expand Up @@ -319,15 +321,11 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {

uint256 totalGasFeesCharged = initialDappPaymasterBalance - resultingDappPaymasterBalance;

if (priceMarkup >= 1e6) {
//priceMarkup
expectedPriceMarkup = totalGasFeesCharged - ((totalGasFeesCharged * 1e6) / priceMarkup);
actualPriceMarkup = resultingFeeCollectorPaymasterBalance - initialFeeCollectorBalance;
} else {
revert("PriceMarkup must be more than 1e6");
}
expectedPriceMarkup = totalGasFeesCharged - ((totalGasFeesCharged * 1e6) / priceMarkup);
actualPriceMarkup = resultingFeeCollectorPaymasterBalance - initialFeeCollectorBalance;
}

// Note: can pack values into one struct
function calculateAndAssertAdjustments(
BiconomySponsorshipPaymaster bicoPaymaster,
uint256 initialDappPaymasterBalance,
Expand All @@ -350,13 +348,10 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
assertEq(expectedPriceMarkup, actualPriceMarkup);
// Gas paid by dapp is higher than paymaster
// Guarantees that EP always has sufficient deposit to pay back dapps

// TODO
// Review: fix this properly. avoid out of stack errors
assertGt(gasPaidByDapp, BUNDLER.addr.balance - initialBundlerBalance);
// Ensure that max 1% difference between total gas paid + the adjustment premium and gas paid by dapp (from
// Ensure that max 2% difference between total gas paid + the adjustment premium and gas paid by dapp (from
// paymaster)
assertApproxEqRel(totalGasFeePaid + actualPriceMarkup, gasPaidByDapp, 0.01e18);
assertApproxEqRel(totalGasFeePaid + actualPriceMarkup, gasPaidByDapp, 0.02e18);
}

function _toSingletonArray(address addr) internal pure returns (address[] memory) {
Expand Down
35 changes: 35 additions & 0 deletions test/base/TestHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,41 @@ contract TestHelper is CheatCodes, EventsAndErrors {
userOp.signature = signature;
}

function buildPackedUserOperation(
Vm.Wallet memory wallet,
Nexus account,
ExecType execType,
Execution[] memory executions,
address validator,
uint256 nonce
)
internal
view
returns (PackedUserOperation[] memory userOps)
{
// Validate execType
require(execType == EXECTYPE_DEFAULT || execType == EXECTYPE_TRY, "Invalid ExecType");

// Initialize the userOps array with one operation
userOps = new PackedUserOperation[](1);

uint256 nonceToUse;
if (nonce == 0) {
nonceToUse = getNonce(address(account), MODE_VALIDATION, validator);
} else {
nonceToUse = nonce;
}

// Build the UserOperation
userOps[0] = buildPackedUserOp(address(account), nonceToUse);
userOps[0].callData = prepareERC7579ExecuteCallData(execType, executions);

// Sign the operation
bytes32 userOpHash = ENTRYPOINT.getUserOpHash(userOps[0]);
userOps[0].signature = signMessage(wallet, userOpHash);
return userOps;
}

/// @notice Retrieves the nonce for a given account and validator
/// @param account The account address
/// @param vMode Validation Mode
Expand Down
Loading
Loading