Skip to content

Commit

Permalink
refs setup
Browse files Browse the repository at this point in the history
  • Loading branch information
livingrockrises committed Apr 26, 2023
1 parent 0c10ed1 commit fba4feb
Show file tree
Hide file tree
Showing 7 changed files with 591 additions and 5 deletions.
148 changes: 148 additions & 0 deletions contracts/references/CandidePaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

/// @author CandideWallet Team

import "@account-abstraction/contracts/core/BasePaymaster.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract CandidePaymaster is BasePaymaster {

using ECDSA for bytes32;
using UserOperationLib for UserOperation;
using SafeERC20 for IERC20Metadata;

enum SponsoringMode {
FULL,
GAS,
FREE
}

struct PaymasterData {
IERC20Metadata token;
SponsoringMode mode;
uint48 validUntil;
uint256 fee;
uint256 exchangeRate;
bytes signature;
}

//calculated cost of the postOp
uint256 constant public COST_OF_POST = 45000;
mapping(IERC20Metadata => uint256) public balances;
//

event UserOperationSponsored(address indexed sender, address indexed token, uint256 cost);

constructor(IEntryPoint _entryPoint, address _owner) BasePaymaster(_entryPoint) {
_transferOwnership(_owner);
}

/**
* withdraw tokens.
* @param token the token deposit to withdraw
* @param target address to send to
* @param amount amount to withdraw
*/
function withdrawTokensTo(IERC20Metadata token, address target, uint256 amount) public {
require(owner() == msg.sender, "CP00: only owner can withdraw tokens");
balances[token] -= amount;
token.safeTransfer(target, amount);
}

function pack(UserOperation calldata userOp) internal pure returns (bytes32) {
return keccak256(abi.encode(
userOp.sender,
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.callGasLimit,
userOp.verificationGasLimit,
userOp.preVerificationGas,
userOp.maxFeePerGas,
userOp.maxPriorityFeePerGas
));
}

/**
* return the hash we're going to sign off-chain (and validate on-chain)
* this method is called by the off-chain service, to sign the request.
* it is called on-chain from the validatePaymasterUserOp, to validate the signature.
* note that this signature covers all fields of the UserOperation, except the "paymasterAndData",
* which will carry the signature itself.
*/
function getHash(UserOperation calldata userOp, PaymasterData memory paymasterData)
public view returns (bytes32) {
return keccak256(abi.encode(
pack(userOp),
block.chainid,
address(this),
address(paymasterData.token),
paymasterData.mode,
paymasterData.validUntil,
paymasterData.fee,
paymasterData.exchangeRate
));
}

function parsePaymasterAndData(bytes calldata paymasterAndData)
public pure returns (PaymasterData memory) {
IERC20Metadata token = IERC20Metadata(address(bytes20(paymasterAndData[20:40])));
SponsoringMode mode = SponsoringMode(uint8(bytes1(paymasterAndData[40:41])));
uint48 validUntil = uint48(bytes6(paymasterAndData[41:47]));
uint256 fee = uint256(bytes32(paymasterAndData[47:79]));
uint256 exchangeRate = uint256(bytes32(paymasterAndData[79:111]));
bytes memory signature = bytes(paymasterAndData[111:]);
return PaymasterData(token, mode, validUntil, fee, exchangeRate, signature);
}

/**
* Verify our external signer signed this request and decode paymasterData
* paymasterData contains the following:
* token address length 20
* signature length 64 or 65
*/
function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
internal virtual override returns (bytes memory context, uint256 validationData){
(userOpHash);

PaymasterData memory paymasterData = parsePaymasterAndData(userOp.paymasterAndData);
require(paymasterData.signature.length == 64 || paymasterData.signature.length == 65, "CP01: invalid signature length in paymasterAndData");

bytes32 _hash = getHash(userOp, paymasterData).toEthSignedMessageHash();
if (owner() != _hash.recover(paymasterData.signature)) {
return ("", _packValidationData(true, paymasterData.validUntil, 0));
}

address account = userOp.getSender();
uint256 gasPriceUserOp = userOp.gasPrice();
bytes memory _context = abi.encode(account, paymasterData.token, paymasterData.mode, paymasterData.fee, paymasterData.exchangeRate, gasPriceUserOp);

return (_context, _packValidationData(false, paymasterData.validUntil, 0));
}

/**
* Perform the post-operation to charge the sender for the gas.
*/
function _postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) internal override {

(address account, IERC20Metadata token, SponsoringMode sponsoringMode, uint256 fee, uint256 exchangeRate, uint256 gasPricePostOp)
= abi.decode(context, (address, IERC20Metadata, SponsoringMode, uint256, uint256, uint256));
if (sponsoringMode == SponsoringMode.FREE) return;
//
uint256 actualTokenCost = ((actualGasCost + (COST_OF_POST * gasPricePostOp)) * exchangeRate) / 1e18;
if (sponsoringMode == SponsoringMode.FULL){
actualTokenCost = actualTokenCost + fee;
}
if (mode != PostOpMode.postOpReverted) {
token.safeTransferFrom(account, address(this), actualTokenCost);
balances[token] += actualTokenCost;
emit UserOperationSponsored(account, address(token), actualTokenCost);
}
}
}
8 changes: 8 additions & 0 deletions contracts/references/soul/IPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IPriceOracle {

function exchangePrice(address _token) external view returns (uint256 price, uint8 decimals);

}
37 changes: 37 additions & 0 deletions contracts/references/soul/ITokenPaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@account-abstraction/contracts/interfaces/IPaymaster.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

interface ITokenPaymaster is IPaymaster, IERC165 {

/**
* @dev Emitted when token is added.
*/
event TokenAdded(address token);

/**
* @dev Emitted when token is removed.
*/
event TokenRemoved(address token);

/**
* @dev Returns the supported entrypoint.
*/
function entryPoint() external view returns (address);


/**
* @dev Returns true if this contract supports the given token address.
*/
function isSupportedToken(address _token) external view returns (bool);


/**
* @dev Returns the exchange price of the token in wei.
*/
function exchangePrice(address _token) external view returns (uint256,uint8);


}
36 changes: 36 additions & 0 deletions contracts/references/soul/PriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "./IPriceOracle.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract PriceOracle is IPriceOracle {
/**
* @notice for security reason, the price feed is immutable
*/
AggregatorV3Interface public immutable priceFeed;

mapping (address => bool) private supportedToken;

constructor(AggregatorV3Interface _priceFeed) {
priceFeed = _priceFeed;
supportedToken[address(0)] = true;
}

function exchangePrice(
address token
) external view override returns (uint256 price, uint8 decimals) {
(token);
(
/* uint80 roundID */,
int256 _price,
/*uint startedAt*/,
/*uint timeStamp*/,
/*uint80 answeredInRound*/
) = priceFeed.latestRoundData();
// price -> uint256
require(_price >= 0, "price is negative");
price = uint256(_price);
decimals = priceFeed.decimals();
}
}
Loading

0 comments on commit fba4feb

Please sign in to comment.