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

feat: Added MultiChainFlexCallDataValidator which allows a param value of callData to be flexible #125

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ typechain-types/
# zerodev orchestra
log/
.envrc

settings.json
1 change: 1 addition & 0 deletions lib/FreshCryptoLib
Submodule FreshCryptoLib added at d9bb3b
1 change: 1 addition & 0 deletions lib/I4337
Submodule I4337 added at dc6485
1 change: 1 addition & 0 deletions lib/p256-verifier
Submodule p256-verifier added at 29475a
2 changes: 1 addition & 1 deletion lib/solady
Submodule solady updated 78 files
+1,094 −988 .gas-snapshot
+2 −0 .github/workflows/ci-all-via-ir.yml
+27 −0 .github/workflows/ci-invariant-intense.yml
+13 −7 .github/workflows/ci.yml
+3 −0 .gitignore
+7 −2 README.md
+6 −2 foundry.toml
+1 −1 package.json
+11 −6 src/Milady.sol
+238 −47 src/accounts/ERC1271.sol
+33 −15 src/accounts/ERC4337.sol
+160 −72 src/accounts/ERC6551.sol
+13 −10 src/accounts/ERC6551Proxy.sol
+232 −0 src/accounts/LibERC6551.sol
+4 −4 src/tokens/ERC1155.sol
+7 −7 src/tokens/ERC4626.sol
+1 −1 src/tokens/ERC721.sol
+5 −0 src/utils/Base64.sol
+1 −1 src/utils/CREATE3.sol
+91 −0 src/utils/DeploylessPredeployQueryer.sol
+755 −4 src/utils/DynamicBufferLib.sol
+630 −0 src/utils/EnumerableSetLib.sol
+173 −92 src/utils/FixedPointMathLib.sol
+10 −9 src/utils/LibBit.sol
+5 −5 src/utils/LibBitmap.sol
+305 −1 src/utils/LibClone.sol
+342 −20 src/utils/LibPRNG.sol
+1 −1 src/utils/LibSort.sol
+3 −3 src/utils/LibString.sol
+324 −39 src/utils/MinHeapLib.sol
+97 −0 src/utils/P256.sol
+7 −7 src/utils/RedBlackTreeLib.sol
+1 −1 src/utils/SSTORE2.sol
+284 −97 src/utils/SafeCastLib.sol
+188 −3 src/utils/SafeTransferLib.sol
+121 −0 src/utils/SignatureCheckerLib.sol
+1 −1 src/utils/UUPSUpgradeable.sol
+190 −0 src/utils/UpgradeableBeacon.sol
+21 −1 test/Base64.t.sol
+157 −0 test/DeploylessPredeployQueryer.t.sol
+136 −0 test/DynamicBufferLib.t.sol
+315 −0 test/ERC1271.t.sol
+5 −1 test/ERC20.t.sol
+124 −63 test/ERC4337.t.sol
+1 −1 test/ERC4337Factory.t.sol
+177 −124 test/ERC4626.t.sol
+95 −51 test/ERC6551.t.sol
+647 −0 test/EnumerableSetLib.t.sol
+247 −14 test/FixedPointMathLib.t.sol
+19 −10 test/LibBitmap.t.sol
+156 −8 test/LibClone.t.sol
+183 −0 test/LibERC6551.t.sol
+440 −21 test/LibPRNG.t.sol
+20 −25 test/LibString.t.sol
+345 −3 test/MinHeapLib.t.sol
+25 −14 test/OwnableRoles.t.sol
+95 −0 test/P256.t.sol
+919 −199 test/SafeCastLib.t.sol
+363 −87 test/SafeTransferLib.t.sol
+212 −0 test/SignatureCheckerLib.t.sol
+197 −0 test/UpgradeableBeacon.t.sol
+184 −0 test/utils/Brutalizer.sol
+36 −155 test/utils/TestPlus.sol
+720 −93 test/utils/forge-std/Test.sol
+1,826 −366 test/utils/forge-std/Vm.sol
+0 −517 test/utils/forge-std/ds-test/test.sol
+2 −8 test/utils/mocks/MockERC1155.sol
+5 −1 test/utils/mocks/MockERC1271Wallet.sol
+2 −8 test/utils/mocks/MockERC20.sol
+2 −15 test/utils/mocks/MockERC2981.sol
+2 −8 test/utils/mocks/MockERC4337.sol
+21 −8 test/utils/mocks/MockERC6551.sol
+2 −8 test/utils/mocks/MockERC6909.sol
+2 −15 test/utils/mocks/MockERC721.sol
+1 −1 test/utils/mocks/MockEntryPoint.sol
+8 −20 test/utils/mocks/MockOwnable.sol
+14 −26 test/utils/mocks/MockOwnableRoles.sol
+2 −8 test/utils/mocks/MockUUPSImplementation.sol
17 changes: 17 additions & 0 deletions src/mock/MockBridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

struct OnchainCrossChainOrder {
uint32 fillDeadline;
bytes32 orderDataType;
bytes orderData;
}

contract MockBridge {
event Open(OnchainCrossChainOrder order);

function open(OnchainCrossChainOrder calldata order) public {
emit Open(order);
}
}
200 changes: 200 additions & 0 deletions src/validator/MultiChainFlexCallDataValidator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {ECDSA} from "solady/utils/ECDSA.sol";
import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol";
import {IValidator, IHook} from "../interfaces/IERC7579Modules.sol";
import {PackedUserOperation} from "../interfaces/PackedUserOperation.sol";
import {IEntryPoint} from "../interfaces/IEntryPoint.sol";
import {
SIG_VALIDATION_SUCCESS_UINT,
SIG_VALIDATION_FAILED_UINT,
MODULE_TYPE_VALIDATOR,
MODULE_TYPE_HOOK,
ERC1271_MAGICVALUE,
ERC1271_INVALID
} from "../types/Constants.sol";

struct ECDSAValidatorStorage {
address owner;
}

struct FlexCallData {
uint32 offset;
bytes value;
}

bytes constant DUMMY_ECDSA_SIG =
hex"fffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c";

contract MultiChainFlexCallDataValidator is IValidator, IHook {
event OwnerRegistered(address indexed kernel, address indexed owner);

mapping(address => ECDSAValidatorStorage) public ecdsaValidatorStorage;

function onInstall(bytes calldata _data) external payable override {
address owner = address(bytes20(_data[0:20]));
ecdsaValidatorStorage[msg.sender].owner = owner;
emit OwnerRegistered(msg.sender, owner);
}

function onUninstall(bytes calldata) external payable override {
if (!_isInitialized(msg.sender)) revert NotInitialized(msg.sender);
delete ecdsaValidatorStorage[msg.sender];
}

function isModuleType(uint256 typeID) external pure override returns (bool) {
return typeID == MODULE_TYPE_VALIDATOR || typeID == MODULE_TYPE_HOOK;
}

function isInitialized(address smartAccount) external view override returns (bool) {
return _isInitialized(smartAccount);
}

function _isInitialized(address smartAccount) internal view returns (bool) {
return ecdsaValidatorStorage[smartAccount].owner != address(0);
}

function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
external
payable
override
returns (uint256)
{
bytes calldata sig = userOp.signature;
address owner = ecdsaValidatorStorage[msg.sender].owner;
if (sig.length == 65) {
// simple ecdsa verification
if (owner == ECDSA.recover(userOpHash, sig)) {
return SIG_VALIDATION_SUCCESS_UINT;
}
bytes32 ethHash = ECDSA.toEthSignedMessageHash(userOpHash);
address recovered = ECDSA.recover(ethHash, sig);
if (owner != recovered) {
return SIG_VALIDATION_FAILED_UINT;
}
return SIG_VALIDATION_SUCCESS_UINT;
}
bytes memory ecdsaSig = sig[0:65];
bytes32 merkleRoot = bytes32(sig[65:97]);
// if the signature is a dummy signature, then use dummyUserOpHash instead of real userOpHash
if (keccak256(ecdsaSig) == keccak256(DUMMY_ECDSA_SIG)) {
(bytes32 dummyUserOpHash, bytes32[] memory proof, FlexCallData[] memory flexCallData) =
abi.decode(sig[97:], (bytes32, bytes32[], FlexCallData[]));

if (flexCallData.length > 0) {
PackedUserOperation memory _userOp = _toMemoryUserOp(userOp);

_userOp.callData = _replaceCallData(userOp.callData, flexCallData);

// just for proper gas estimation
bytes32 _useless = IEntryPoint(0x0000000071727De22E5E9d8BAf0edAc6f37da032).getUserOpHash(_userOp);
}

require(MerkleProofLib.verify(proof, merkleRoot, dummyUserOpHash), "hash is not in proof");
// otherwise, use real userOpHash
} else {
(bytes32[] memory proof, FlexCallData[] memory flexCallData) =
abi.decode(sig[97:], (bytes32[], FlexCallData[]));
bytes32 modifiedUserOpHash = userOpHash;
if (flexCallData.length > 0) {
PackedUserOperation memory _userOp = _toMemoryUserOp(userOp);

_userOp.callData = _replaceCallData(userOp.callData, flexCallData);
_userOp.paymasterAndData = hex"";
modifiedUserOpHash = IEntryPoint(0x0000000071727De22E5E9d8BAf0edAc6f37da032).getUserOpHash(_userOp);
}
require(MerkleProofLib.verify(proof, merkleRoot, modifiedUserOpHash), "hash is not in proof");
}
// simple ecdsa verification
if (owner == ECDSA.recover(merkleRoot, ecdsaSig)) {
return SIG_VALIDATION_SUCCESS_UINT;
}
bytes32 ethRoot = ECDSA.toEthSignedMessageHash(merkleRoot);
address merkleRecovered = ECDSA.recover(ethRoot, ecdsaSig);
if (owner != merkleRecovered) {
return SIG_VALIDATION_FAILED_UINT;
}
return SIG_VALIDATION_SUCCESS_UINT;
}

function isValidSignatureWithSender(address, bytes32 hash, bytes calldata sig)
external
view
override
returns (bytes4)
{
address owner = ecdsaValidatorStorage[msg.sender].owner;
if (sig.length == 65) {
// simple ecdsa verification
if (owner == ECDSA.recover(hash, sig)) {
return ERC1271_MAGICVALUE;
}
bytes32 ethHash = ECDSA.toEthSignedMessageHash(hash);
address recovered = ECDSA.recover(ethHash, sig);
if (owner != recovered) {
return ERC1271_INVALID;
}
return ERC1271_MAGICVALUE;
}
bytes memory ecdsaSig = sig[0:65];
bytes32 merkleRoot = bytes32(sig[65:97]);
bytes32[] memory proof = abi.decode(sig[97:], (bytes32[]));
require(MerkleProofLib.verify(proof, merkleRoot, hash), "hash is not in proof");
// simple ecdsa verification
if (owner == ECDSA.recover(merkleRoot, ecdsaSig)) {
return ERC1271_MAGICVALUE;
}
bytes32 ethRoot = ECDSA.toEthSignedMessageHash(merkleRoot);
address merkleRecovered = ECDSA.recover(ethRoot, ecdsaSig);
if (owner != merkleRecovered) {
return ERC1271_INVALID;
}
return ERC1271_MAGICVALUE;
}

function preCheck(address msgSender, uint256 value, bytes calldata)
external
payable
override
returns (bytes memory)
{
require(msgSender == ecdsaValidatorStorage[msg.sender].owner, "ECDSAValidator: sender is not owner");
return hex"";
}

function postCheck(bytes calldata hookData) external payable override {}

function _toMemoryUserOp(PackedUserOperation calldata userOp) internal pure returns (PackedUserOperation memory) {
return PackedUserOperation({
sender: userOp.sender,
nonce: userOp.nonce,
initCode: userOp.initCode,
callData: userOp.callData,
accountGasLimits: userOp.accountGasLimits,
preVerificationGas: userOp.preVerificationGas,
gasFees: userOp.gasFees,
paymasterAndData: userOp.paymasterAndData,
signature: userOp.signature
});
}

function _replaceCallData(bytes memory originalCallData, FlexCallData[] memory flexCallDataArray)
internal
pure
returns (bytes memory)
{
bytes memory modifiedCallData = originalCallData;
for (uint256 i = 0; i < flexCallDataArray.length; i++) {
FlexCallData memory flexData = flexCallDataArray[i];
require(flexData.offset + flexData.value.length <= originalCallData.length, "FlexCallData out of bounds");
// Should not overwrite the first 4 bytes sig of the callData
require(flexData.offset > 4, "FlexCallData offset too small");
for (uint256 j = 0; j < flexData.value.length && j < flexData.value.length; j++) {
modifiedCallData[flexData.offset + j] = flexData.value[j];
}
}
return modifiedCallData;
}
}
Loading