Skip to content

Commit

Permalink
Merge pull request #6 from permissivelabs/v0.0.4
Browse files Browse the repository at this point in the history
V0.0.4
  • Loading branch information
Flydexo authored Jun 11, 2023
2 parents 8c91bcb + de281ae commit eda8be8
Show file tree
Hide file tree
Showing 21 changed files with 945 additions and 438 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ ETHERSCAN_URL_59140=""
ETHERSCAN_KEY_59140=""
FEE_MANAGER="0x2ad29cc25d9fb3647b304353a0a609f27afe380b"
ALLOWANCE_CALLDATA="0xEf86319d72267e538e963f724cdC813d8CCC6C50"
SALT="Permissive-v0.0.3"
SALT="Permissive-v0.0.4"
BYTES_LIB="0xaEFF1a8e8Fc46A37151b4be60F3430742d9ccBF7"
ECDSA="0xF9e2a0dC96342809dC6412E74812659c6f61C70B"
14 changes: 12 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,15 @@ out = 'out'
libs = ['lib']
solc_version = "0.8.18"
optimizer = true
optimizer_runs = 20_000
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
optimizer_runs = 200
gas_reports = ["AllowanceCalldata", "FeeManager", "PermissiveAccount", "PermissiveFactory"]
remappings = [
"Solidity-RLP/=lib/Solidity-RLP/contracts/",
"account-abstraction/=lib/account-abstraction/contracts/",
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"@openzeppelin/=lib/openzeppelin-contracts/",
"bytes/=lib/solidity-bytes-utils/contracts/",
"safe/=lib/safe-contracts/contracts/",
"zerodev/=lib/zerodev-wallet-kernel/src/"
]
8 changes: 0 additions & 8 deletions remappings.txt

This file was deleted.

45 changes: 33 additions & 12 deletions src/core/AllowanceCalldata.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.18;

import "Solidity-RLP/RLPReader.sol";
import "forge-std/console.sol";

uint256 constant ANY = 0;
uint256 constant NE = 1;
Expand All @@ -13,24 +14,31 @@ uint256 constant OR = 6;

library AllowanceCalldata {
function sliceRLPItems(RLPReader.RLPItem[] memory arguments, uint256 start)
internal
public
pure
returns (RLPReader.RLPItem[] memory newArguments)
{
newArguments = new RLPReader.RLPItem[](arguments.length - start);
uint256 initialStart = start;
for (; start < arguments.length; start++) {
newArguments[start - initialStart] = arguments[start];
uint256 length = arguments.length - start;
assembly {
newArguments := mload(0x40)
mstore(0x40, add(newArguments, 0x20))
mstore(newArguments, length)
let hit := mul(add(length, 1), 0x20)
let memStart := add(arguments, mul(start, 0x20))
for { let i := 0x20 } lt(i, hit) { i := add(i, 0x20) } {
mstore(add(newArguments, i), mload(add(memStart, i)))
}
mstore(0x40, add(mload(0x40), mul(length, 0x20)))
}
}

function validateArguments(
RLPReader.RLPItem[] memory allowedArguments,
RLPReader.RLPItem[] memory arguments,
bool isOr
) internal view returns (bool canPass) {
) public view returns (bool canPass) {
if (allowedArguments.length == 0) return true;
for (uint256 i = 0; i < allowedArguments.length; i++) {
for (uint256 i = 0; i < allowedArguments.length; i = unsafe_inc(i)) {
RLPReader.RLPItem[] memory prefixAndArg = RLPReader.toList(allowedArguments[i]);
uint256 prefix = RLPReader.toUint(prefixAndArg[0]);

Expand All @@ -49,15 +57,15 @@ library AllowanceCalldata {
} else if (prefix == OR) {
RLPReader.RLPItem[] memory subAllowance = RLPReader.toList(prefixAndArg[1]);
canPass = validateArguments(subAllowance, sliceRLPItems(arguments, i), true);
i++;
i = unsafe_inc(i);
} else if (prefix == NE) {
bytes memory allowedArgument = RLPReader.toBytes(prefixAndArg[1]);
bytes memory argument = RLPReader.toBytes(arguments[i]);
canPass = keccak256(allowedArgument) != keccak256(argument);
} else if (prefix == AND) {
RLPReader.RLPItem[] memory subAllowance = RLPReader.toList(prefixAndArg[1]);
canPass = validateArguments(subAllowance, sliceRLPItems(arguments, i), false);
i++;
i = unsafe_inc(i);
} else {
revert("Invalid calldata prefix");
}
Expand All @@ -68,22 +76,35 @@ library AllowanceCalldata {
return canPass;
}

function isAllowedCalldata(bytes memory allowed, bytes memory data) internal view returns (bool isOk) {
function isAllowedCalldata(bytes memory allowed, bytes memory data, uint256 value)
external
view
returns (bool isOk)
{
RLPReader.RLPItem memory RLPAllowed = RLPReader.toRlpItem(allowed);
RLPReader.RLPItem[] memory allowedArguments = RLPReader.toList(RLPAllowed);
RLPReader.RLPItem memory RLPData = RLPReader.toRlpItem(data);
RLPReader.RLPItem[] memory arguments = RLPReader.toList(RLPData);
if (allowedArguments.length != arguments.length) {
revert("Invalid arguments length");
}
if (value != RLPReader.toUint(arguments[0])) {
revert("msg.value not corresponding to allowed value");
}
isOk = validateArguments(allowedArguments, arguments, false);
}

function RLPtoABI(bytes memory data) internal pure returns (bytes memory abiEncoded) {
function RLPtoABI(bytes memory data) external pure returns (bytes memory abiEncoded) {
RLPReader.RLPItem memory RLPData = RLPReader.toRlpItem(data);
RLPReader.RLPItem[] memory arguments = RLPReader.toList(RLPData);
for (uint256 i = 0; i < arguments.length; i++) {
for (uint256 i = 1; i < arguments.length; i = unsafe_inc(i)) {
abiEncoded = bytes.concat(abiEncoded, RLPReader.toBytes(arguments[i]));
}
}

function unsafe_inc(uint256 i) private pure returns (uint256) {
unchecked {
return i + 1;
}
}
}
8 changes: 5 additions & 3 deletions src/core/FeeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity ^0.8.18;
import "@openzeppelin/contracts/access/Ownable.sol";

contract FeeManager is Ownable {
uint256 public fee = 2000;
uint24 public fee = 2000;
bool initialized;

event FeePaid(address indexed from, uint256 amount);
Expand All @@ -16,11 +16,13 @@ contract FeeManager is Ownable {
initialized = true;
}

function setFee(uint256 _fee) external onlyOwner {
function setFee(uint24 _fee) external {
_checkOwner();
fee = _fee;
}

function withdraw() external onlyOwner {
function withdraw() external {
_checkOwner();
payable(msg.sender).transfer(address(this).balance);
}

Expand Down
108 changes: 54 additions & 54 deletions src/core/PermissiveAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,34 @@
pragma solidity ^0.8.18;

import "account-abstraction/core/BaseAccount.sol";
import "account-abstraction/core/Helpers.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "../interfaces/IPermissiveAccount.sol";
import "account-abstraction/interfaces/IEntryPoint.sol";
import "../interfaces/Permission.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./AllowanceCalldata.sol";
import "bytes/BytesLib.sol";
import "./FeeManager.sol";
import "../interfaces/IDataValidator.sol";

// keccak256("OperatorPermissions(address operator,bytes32 merkleRootPermissions,uint256 maxValue,uint256 maxFee)")
bytes32 constant typedStruct = 0xcd3966ea44fb027b668c722656f7791caa71de9073b3cbb77585cc6fa97ce82e;
// keccak256("PermissionSet(address operator,bytes32 merkleRootPermissions)")
bytes32 constant typedStruct = 0xd7e1e23484f808c5620ce8d904e88d7540a3eeb37ac94e636726ed53571e4e3c;

contract PermissiveAccount is BaseAccount, IPermissiveAccount, Ownable, EIP712 {
using ECDSA for bytes32;
using BytesLib for bytes;
using PermissionLib for PermissionLib.Permission;

mapping(address => uint256) public remainingFeeForOperator;
mapping(address => uint256) public remainingValueForOperator;
mapping(address => bytes32) public operatorPermissions;
mapping(bytes32 => uint256) public remainingPermUsage;
IEntryPoint private immutable _entryPoint;
FeeManager private immutable feeManager;
bool private _initialized;

constructor(address __entryPoint, address payable _feeManager) EIP712("Permissive Account", "v0.0.3") {
constructor(address __entryPoint, address payable _feeManager) EIP712("Permissive Account", "v0.0.4") {
_entryPoint = IEntryPoint(__entryPoint);
feeManager = FeeManager(_feeManager);
}
Expand All @@ -49,22 +49,14 @@ contract PermissiveAccount is BaseAccount, IPermissiveAccount, Ownable, EIP712 {
_transferOwnership(owner);
}

function setOperatorPermissions(
address operator,
bytes32 merkleRootPermissions,
uint256 maxValue,
uint256 maxFee,
bytes calldata signature
) external {
function setOperatorPermissions(PermissionSet calldata permSet, bytes calldata signature) external {
bytes32 digest =
_hashTypedDataV4(keccak256(abi.encode(typedStruct, operator, merkleRootPermissions, maxValue, maxFee)));
_hashTypedDataV4(keccak256(abi.encode(typedStruct, permSet.operator, permSet.merkleRootPermissions)));
address signer = ECDSA.recover(digest, signature);
if (signer != owner()) revert NotAllowed(signer);
bytes32 oldValue = operatorPermissions[operator];
operatorPermissions[operator] = merkleRootPermissions;
remainingFeeForOperator[operator] = maxFee;
remainingValueForOperator[operator] = maxValue;
emit OperatorMutated(operator, oldValue, merkleRootPermissions, maxValue, maxFee);
if (signer != owner()) revert("Not Allowed");
bytes32 oldValue = operatorPermissions[permSet.operator];
operatorPermissions[permSet.operator] = permSet.merkleRootPermissions;
emit OperatorMutated(permSet.operator, oldValue, permSet.merkleRootPermissions);
}

function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
Expand All @@ -77,18 +69,31 @@ contract PermissiveAccount is BaseAccount, IPermissiveAccount, Ownable, EIP712 {
if (owner() != hash.recover(userOp.signature)) {
(,,, PermissionLib.Permission memory permission, bytes32[] memory proof, uint256 providedFee) =
abi.decode(userOp.callData[4:], (address, uint256, bytes, PermissionLib.Permission, bytes32[], uint256));
if (permission.operator != hash.recover(userOp.signature)) {
validationData = SIG_VALIDATION_FAILED;
if (permission.operator.code.length > 0) {
try IERC1271(permission.operator).isValidSignature(hash, userOp.signature) returns (bytes4 magicValue) {
validationData = _packValidationData(
ValidationData(
magicValue == IERC1271.isValidSignature.selector ? address(0) : address(1),
permission.validAfter,
permission.validUntil
)
);
} catch {
validationData =
_packValidationData(ValidationData(address(1), permission.validAfter, permission.validUntil));
}
} else if (permission.operator != hash.recover(userOp.signature)) {
return SIG_VALIDATION_FAILED;
} else {
validationData =
_packValidationData(ValidationData(address(0), permission.validAfter, permission.validUntil));
}
bytes32 permHash = permission.hash();
_validateMerklePermission(permission, proof, permHash);
_validatePermission(userOp, permission, permHash);
_validateData(permission);
uint256 gasFee = computeGasFee(userOp);
if (providedFee != gasFee) revert("Invalid provided fee");
if (gasFee > remainingFeeForOperator[permission.operator]) {
revert("Exceeded Fees");
}
remainingFeeForOperator[permission.operator] -= gasFee;
}
_payPrefund(missingAccountFunds);
emit UserOpValidated(userOpHash, userOp);
Expand All @@ -104,17 +109,6 @@ contract PermissiveAccount is BaseAccount, IPermissiveAccount, Ownable, EIP712 {
uint256 gasFee
) external {
_requireFromEntryPointOrOwner();
if (msg.sender != owner()) {
if (permission.expiresAtUnix != 0) {
if (block.timestamp >= permission.expiresAtUnix) {
revert ExpiredPermission(block.timestamp, permission.expiresAtUnix);
}
} else if (permission.expiresAtBlock != 0) {
if (block.number >= permission.expiresAtBlock) {
revert ExpiredPermission(block.number, permission.expiresAtBlock);
}
}
}
payable(address(feeManager)).transfer((gasFee * feeManager.fee()) / 10000);
(bool success, bytes memory result) = dest.call{value: value}(
bytes.concat(func.slice(0, 4), AllowanceCalldata.RLPtoABI(func.slice(4, func.length - 4)))
Expand Down Expand Up @@ -142,6 +136,17 @@ contract PermissiveAccount is BaseAccount, IPermissiveAccount, Ownable, EIP712 {
return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
}

function _validateData(PermissionLib.Permission memory permission) internal view {
if (
permission.dataValidation.validator != address(0)
&& !IDataValidator(permission.dataValidation.validator).isValidData(
permission.dataValidation.target, permission.dataValidation.data
)
) {
revert("Invalid data");
}
}

function _validatePermission(
UserOperation calldata userOp,
PermissionLib.Permission memory permission,
Expand All @@ -150,29 +155,24 @@ contract PermissiveAccount is BaseAccount, IPermissiveAccount, Ownable, EIP712 {
(address to, uint256 value, bytes memory callData,,) =
abi.decode(userOp.callData[4:], (address, uint256, bytes, PermissionLib.Permission, bytes32[]));
if (permission.to != to) revert("InvalidTo");
if (remainingValueForOperator[permission.operator] < value) {
revert("ExceededValue");
}
remainingValueForOperator[permission.operator] -= value;
uint256 rPermU = remainingPermUsage[permHash];
if (permission.maxUsage > 0) {
if (permission.maxUsage == 1) revert("OutOfPerms");
if (remainingPermUsage[permission.hash()] == 1) {
if (rPermU == 1) {
revert("OutOfPerms2");
}
if (remainingPermUsage[permHash] == 0) {
remainingPermUsage[permHash] = permission.maxUsage;
if (rPermU == 0) {
rPermU = permission.maxUsage;
}
remainingPermUsage[permHash]--;
rPermU--;
remainingPermUsage[permHash] = rPermU;
}
require(
AllowanceCalldata.isAllowedCalldata(permission.allowed_arguments, callData.slice(4, callData.length - 4))
== true,
"Not allowed Calldata"
);
if (
!AllowanceCalldata.isAllowedCalldata(
permission.allowed_arguments, callData.slice(4, callData.length - 4), value
)
) revert("Not allowed Calldata");
if (permission.selector != bytes4(callData)) revert("InvalidSelector");
if (permission.expiresAtUnix != 0 && permission.expiresAtBlock != 0) {
revert("InvalidPermission");
}
if (permission.paymaster != address(0)) {
address paymaster = address(0);
assembly {
Expand All @@ -187,7 +187,7 @@ contract PermissiveAccount is BaseAccount, IPermissiveAccount, Ownable, EIP712 {
PermissionLib.Permission memory permission,
bytes32[] memory proof,
bytes32 permHash
) public view {
) internal view {
bool isValidProof =
MerkleProof.verify(proof, operatorPermissions[permission.operator], keccak256(bytes.concat(permHash)));
if (!isValidProof) revert("Invalid Proof");
Expand Down
Loading

0 comments on commit eda8be8

Please sign in to comment.