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

Temp remove post op cost and add tests #6

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
30 changes: 30 additions & 0 deletions contracts/common/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,36 @@ contract BiconomySponsorshipPaymasterErrors {
*/
error VerifyingSignerCanNotBeContract();

/**
* @notice Throws when ETH withdrawal fails
*/
error WithdrawalFailed();

/**
* @notice Throws when insufficient funds to withdraw
*/
error InsufficientFundsInGasTank();

/**
* @notice Throws when invalid signature length in paymasterAndData
*/
error InvalidSignatureLength();

/**
* @notice Throws when invalid signature length in paymasterAndData
*/
error InvalidPriceMarkup();

/**
* @notice Throws when insufficient funds for paymasterid
*/
error InsufficientFundsForPaymasterId();

/**
* @notice Throws when calling deposit()
*/
error UseDepositForInstead();

/**
* @notice Throws when trying to withdraw to address(0)
*/
Expand Down
103 changes: 43 additions & 60 deletions contracts/sponsorship/SponsorshipPaymasterWithPremium.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ contract BiconomySponsorshipPaymaster is

address public verifyingSigner;
address public feeCollector;
uint48 public postopCost;
uint32 private constant PRICE_DENOMINATOR = 1e6;

// note: could rename to PAYMASTER_ID_OFFSET
Expand All @@ -55,8 +54,11 @@ contract BiconomySponsorshipPaymaster is
)
BasePaymaster(_owner, _entryPoint)
{
// TODO
// Check for zero address
if (_verifyingSigner == address(0)) {
revert VerifyingSignerCanNotBeZero();
} else if (_feeCollector == address(0)) {
revert FeeCollectorCanNotBeZero();
}
verifyingSigner = _verifyingSigner;
feeCollector = _feeCollector;
}
Expand Down Expand Up @@ -117,23 +119,11 @@ contract BiconomySponsorshipPaymaster is
emit FeeCollectorChanged(oldFeeCollector, _newFeeCollector, msg.sender);
}

/**
* @dev Set a new unaccountedEPGasOverhead value.
* @param value The new value to be set as the unaccountedEPGasOverhead.
* @notice only to be called by the owner of the contract.
*/
function setPostopCost(uint48 value) external payable onlyOwner {
require(value <= 200_000, "Gas overhead too high");
uint256 oldValue = postopCost;
postopCost = value;
emit PostopCostChanged(oldValue, value);
}

/**
* @dev Override the default implementation.
*/
function deposit() external payable virtual override {
revert("Use depositFor() instead");
revert UseDepositForInstead();
}

/**
Expand All @@ -155,15 +145,19 @@ contract BiconomySponsorshipPaymaster is
function withdrawTo(address payable withdrawAddress, uint256 amount) external override nonReentrant {
if (withdrawAddress == address(0)) revert CanNotWithdrawToZeroAddress();
uint256 currentBalance = paymasterIdBalances[msg.sender];
require(amount <= currentBalance, "Sponsorship Paymaster: Insufficient funds to withdraw from gas tank");
if (amount > currentBalance) {
revert InsufficientFundsInGasTank();
Copy link
Contributor

Choose a reason for hiding this comment

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

make sure to add test cases

}
paymasterIdBalances[msg.sender] = currentBalance - amount;
entryPoint.withdrawTo(withdrawAddress, amount);
emit GasWithdrawn(msg.sender, withdrawAddress, amount);
}

function withdrawEth(address payable recipient, uint256 amount) external onlyOwner {
function withdrawEth(address payable recipient, uint256 amount) external onlyOwner nonReentrant {
(bool success,) = recipient.call{ value: amount }("");
require(success, "withdraw failed");
if (!success) {
revert WithdrawalFailed();
}
}

/**
Expand Down Expand Up @@ -225,47 +219,38 @@ contract BiconomySponsorshipPaymaster is
bytes calldata signature
)
{
paymasterId = address(bytes20(paymasterAndData[VALID_PND_OFFSET:VALID_PND_OFFSET + 20]));
validUntil = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 20:VALID_PND_OFFSET + 26]));
validAfter = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 26:VALID_PND_OFFSET + 32]));
priceMarkup = uint32(bytes4(paymasterAndData[VALID_PND_OFFSET + 32:VALID_PND_OFFSET + 36]));
signature = paymasterAndData[VALID_PND_OFFSET + 36:];
unchecked {
paymasterId = address(bytes20(paymasterAndData[VALID_PND_OFFSET:VALID_PND_OFFSET + 20]));
validUntil = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 20:VALID_PND_OFFSET + 26]));
validAfter = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 26:VALID_PND_OFFSET + 32]));
priceMarkup = uint32(bytes4(paymasterAndData[VALID_PND_OFFSET + 32:VALID_PND_OFFSET + 36]));
signature = paymasterAndData[VALID_PND_OFFSET + 36:];
}
}

/// @notice Performs post-operation tasks, such as deducting the sponsored gas cost from the paymasterId's balance
/// @dev This function is called after a user operation has been executed or reverted.
/// @param context The context containing the token amount and user sender address.
/// @param actualGasCost The actual gas cost of the transaction.
/// @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas
// and maxPriorityFee (and basefee)
// It is not the same as tx.gasprice, which is what the bundler pays.
function _postOp(
PostOpMode,
bytes calldata context,
uint256 actualGasCost,
uint256 actualUserOpFeePerGas
)
internal
override
{
function _postOp(PostOpMode, bytes calldata context, uint256 actualGasCost, uint256) internal override {
unchecked {
(address paymasterId, uint32 dynamicMarkup, bytes32 userOpHash) =
(address paymasterId, uint32 dynamicAdjustment, bytes32 userOpHash) =
abi.decode(context, (address, uint32, bytes32));

uint256 balToDeduct = actualGasCost + postopCost * actualUserOpFeePerGas;
uint256 adjustedGasCost = (actualGasCost * dynamicAdjustment) / PRICE_DENOMINATOR;
Copy link
Contributor

Choose a reason for hiding this comment

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

dynamicAdjustment can be worded better.


uint256 costIncludingPremium = (balToDeduct * dynamicMarkup) / PRICE_DENOMINATOR;
// Deduct the adjusted cost
paymasterIdBalances[paymasterId] -= adjustedGasCost;

// deduct with premium
paymasterIdBalances[paymasterId] -= costIncludingPremium;
if (adjustedGasCost > actualGasCost) {
// Add premium to fee
uint256 premium = adjustedGasCost - actualGasCost;
paymasterIdBalances[feeCollector] += premium;
// Review if we should emit adjustedGasCost as well
emit PremiumCollected(paymasterId, premium);
}

uint256 actualPremium = costIncludingPremium - balToDeduct;
// "collect" premium
paymasterIdBalances[feeCollector] += actualPremium;

emit GasBalanceDeducted(paymasterId, costIncludingPremium, userOpHash);
// Review if we should emit balToDeduct as well
emit PremiumCollected(paymasterId, actualPremium);
emit GasBalanceDeducted(paymasterId, adjustedGasCost, userOpHash);
}
}

Expand Down Expand Up @@ -294,10 +279,9 @@ contract BiconomySponsorshipPaymaster is
//ECDSA library supports both 64 and 65-byte long signatures.
// we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and
// not "ECDSA"
require(
signature.length == 64 || signature.length == 65,
"VerifyingPaymaster: invalid signature length in paymasterAndData"
);
if (signature.length != 64 && signature.length != 65) {
revert InvalidSignatureLength();
}

bool validSig = verifyingSigner.isValidSignatureNow(
ECDSA_solady.toEthSignedMessageHash(getHash(userOp, paymasterId, validUntil, validAfter, priceMarkup)),
Expand All @@ -309,18 +293,17 @@ contract BiconomySponsorshipPaymaster is
return ("", _packValidationData(true, validUntil, validAfter));
}

require(priceMarkup <= 2e6 && priceMarkup > 0, "Sponsorship Paymaster: Invalid markup %");

uint256 maxFeePerGas = userOp.unpackMaxFeePerGas();
if (priceMarkup > 2e6 || priceMarkup == 0) {
revert InvalidPriceMarkup();
Copy link
Contributor

Choose a reason for hiding this comment

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

make sure these are caught properly all the way bubbled up to entry point.
by writing negative test cases

}

// Send 1e6 for No markup
// Send between 0 and 1e6 for discount
uint256 effectiveCost = ((requiredPreFund + (postopCost * maxFeePerGas)) * priceMarkup) / PRICE_DENOMINATOR;
uint256 effectiveCost = (requiredPreFund * priceMarkup) / PRICE_DENOMINATOR;

require(
effectiveCost <= paymasterIdBalances[paymasterId],
"Sponsorship Paymaster: paymasterId does not have enough deposit"
);
if (effectiveCost > paymasterIdBalances[paymasterId]) {
revert InsufficientFundsForPaymasterId();
Copy link
Contributor

Choose a reason for hiding this comment

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

write a test that catches this from entrypoint handleOps execution.

}

context = abi.encode(paymasterId, priceMarkup, userOpHash);

Expand Down
5 changes: 4 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT
bytecode_hash = "none"
evm_version = "paris" # See https://www.evmdiff.com/features?name=PUSH0&kind=opcode
fuzz = { runs = 1_000 }
gas_reports = ["*"]
optimizer = true
optimizer_runs = 1_000_000
Expand All @@ -19,6 +18,10 @@
gas_reports_ignore = ["LockTest"]
via_ir = true

[fuzz]
runs = 1_000
max_test_rejects = 1_000_000

[profile.ci]
fuzz = { runs = 10_000 }
verbosity = 4
Expand Down
Loading
Loading