Skip to content

Commit

Permalink
improve user op handling in outbox, remove private key from script ma…
Browse files Browse the repository at this point in the history
…nagement, deploy mock account
  • Loading branch information
jackchuma committed Feb 10, 2025
1 parent a83556e commit e952c9d
Show file tree
Hide file tree
Showing 10 changed files with 534 additions and 289 deletions.
35 changes: 23 additions & 12 deletions contracts/Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
-include .env

.PHONY: test
# ACCOUNT=mock-account-deployer
ACCOUNT=testnet-admin

ARBITRUM_REQUEST_HASH = 0xd2a7d400b8bf591dfa337aced6d4bab04e979ef9d183c9fc30bc1c6c0ce552aa
OPTIMISM_REQUEST_HASH = 0xe38ad8c9e84178325f28799eb3aaae72551b2eea7920c43d88854edd350719f5
FULFILLER_ADDRESS = 0x23214A0864FC0014CAb6030267738F01AFfdd547
MOCK_VERIFIER_ADDRESS = 0xdac62f96404AB882F5a61CFCaFb0C470a19FC514

# Default Anvil Keys
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
CHAIN_A_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
CHAIN_B_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
MOCK_ACCOUNT = 0x2c4d5B2d8B7ba9e15F09Da8fD455E312bF774Eeb

CHAIN_A_URL=http://localhost:8546
CHAIN_B_URL=http://localhost:8547
Expand All @@ -22,31 +18,46 @@ BASE_RPC = $(BASE_SEPOLIA_RPC)
SUBMIT_REQUEST_RPC = $(OPTIMISM_RPC)
OP_STACK_RPC = $(BASE_RPC)

.PHONY: test
test:
forge fmt
forge test

.PHONY: coverage
coverage:
forge fmt
forge coverage

.PHONY: deploy-mock
deploy-mock:
forge create --rpc-url $(ARBITRUM_RPC) --private-key $(PRIVATE_KEY) test/mocks/MockVerifier.sol:MockVerifier --broadcast -vvvv
forge create --rpc-url $(ARBITRUM_RPC) --account $(ACCOUNT) test/mocks/MockVerifier.sol:MockVerifier --broadcast -vvvv

.PHONY: deploy-mock-account
deploy-mock-account:
forge create --rpc-url $(ARBITRUM_RPC) --account $(ACCOUNT) test/mocks/MockAccount.sol:MockAccount --broadcast -vvvv
forge create --rpc-url $(OPTIMISM_RPC) --account $(ACCOUNT) test/mocks/MockAccount.sol:MockAccount --broadcast -vvvv
forge create --rpc-url $(BASE_RPC) --account $(ACCOUNT) test/mocks/MockAccount.sol:MockAccount --broadcast -vvvv

.PHONY: read-mock
read-mock:
cast call $(MOCK_VERIFIER_ADDRESS) "getFulfillmentInfo(bytes32)(uint96,address)" $(ARBITRUM_REQUEST_HASH) --rpc-url $(ARBITRUM_RPC)

.PHONY: set-mock
set-mock:
cast send $(MOCK_VERIFIER_ADDRESS) "storeFulfillmentInfo(bytes32,address)" $(ARBITRUM_REQUEST_HASH) $(FULFILLER_ADDRESS) --rpc-url $(ARBITRUM_RPC) --private-key $(PRIVATE_KEY)
cast send $(MOCK_VERIFIER_ADDRESS) "storeFulfillmentInfo(bytes32,address)" $(ARBITRUM_REQUEST_HASH) $(FULFILLER_ADDRESS) --rpc-url $(ARBITRUM_RPC) --account $(ACCOUNT)

.PHONY: deploy-arbitrum-sepolia
deploy-arbitrum-sepolia:
forge script script/chains/DeployArbitrum.s.sol:DeployArbitrum --rpc-url $(ARBITRUM_RPC) --broadcast -vvvv

.PHONY: deploy-op-stack
deploy-op-stack:
PRIVATE_KEY=$(PRIVATE_KEY) forge script script/chains/DeployBase.s.sol:DeployBase --rpc-url $(OP_STACK_RPC) --broadcast -vvvv
forge script script/chains/DeployBase.s.sol:DeployBase --rpc-url $(OP_STACK_RPC) --account $(ACCOUNT) --broadcast -vvvv

.PHONY: submit-request
submit-request:
PRIVATE_KEY=$(PRIVATE_KEY) forge script script/actions/SubmitRequest.s.sol:SubmitRequest --rpc-url $(SUBMIT_REQUEST_RPC) --broadcast -vvvv
forge script script/actions/SubmitRequest.s.sol:SubmitRequest --rpc-url $(SUBMIT_REQUEST_RPC) --account $(ACCOUNT) --broadcast -vvvv

.PHONY: fulfill-request
fulfill-request:
PRIVATE_KEY=$(PRIVATE_KEY) forge script script/actions/SubmitToInbox.s.sol:SubmitToInbox --rpc-url $(OPTIMISM_RPC) --broadcast -vvvv
forge script script/actions/SubmitToInbox.s.sol:SubmitToInbox --rpc-url $(OPTIMISM_RPC) --account $(ACCOUNT) --broadcast -vvvv
26 changes: 17 additions & 9 deletions contracts/script/HelperConfig.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {Script} from "forge-std/Script.sol";

import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";

import {MockAccount} from "../test/mocks/MockAccount.sol";

contract HelperConfig is Script {
struct NetworkConfig {
uint256 chainId;
Expand All @@ -14,8 +16,9 @@ contract HelperConfig is Script {
address inbox;
address l2Oracle;
address shoyuBashi;
uint256 deployerKey;
address entryPoint;
address smartAccount;
string rpcUrl;
}

uint256 public DEFAULT_ANVIL_PRIVATE_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
Expand Down Expand Up @@ -48,8 +51,9 @@ contract HelperConfig is Script {
inbox: 0xAF8e568F4E3105e1D8818B26dCA57CD4bd753695,
l2Oracle: 0x042B2E6C5E99d4c521bd49beeD5E99651D9B0Cf4,
shoyuBashi: 0xce8b068D4F7F2eb3bDAFa72eC3C4feE78CF9Ccf7,
deployerKey: vm.envUint("PRIVATE_KEY"),
entryPoint: 0x0000000071727De22E5E9d8BAf0edAc6f37da032
entryPoint: 0x0000000071727De22E5E9d8BAf0edAc6f37da032,
smartAccount: 0x2c4d5B2d8B7ba9e15F09Da8fD455E312bF774Eeb,
rpcUrl: vm.envString("ARBITRUM_SEPOLIA_RPC")
});
}

Expand All @@ -62,8 +66,9 @@ contract HelperConfig is Script {
inbox: 0x8e993853C303288f4fcd138E180E31a3c798E4F9,
l2Oracle: 0x4C8BA32A5DAC2A720bb35CeDB51D6B067D104205,
shoyuBashi: 0x6602dc9b6bd964C2a11BBdA9B2275308D1Bbc14f,
deployerKey: vm.envUint("PRIVATE_KEY"),
entryPoint: 0x0000000071727De22E5E9d8BAf0edAc6f37da032
entryPoint: 0x0000000071727De22E5E9d8BAf0edAc6f37da032,
smartAccount: 0x2c4d5B2d8B7ba9e15F09Da8fD455E312bF774Eeb,
rpcUrl: vm.envString("BASE_SEPOLIA_RPC")
});
}

Expand All @@ -76,13 +81,15 @@ contract HelperConfig is Script {
inbox: 0x9435B271fB6b525B87171F92379A5c85fEF4d4cB,
l2Oracle: 0x218CD9489199F321E1177b56385d333c5B598629,
shoyuBashi: 0x7237bb8d1d38DF8b473b5A38eD90088AF162ad8e,
deployerKey: vm.envUint("PRIVATE_KEY"),
entryPoint: 0x0000000071727De22E5E9d8BAf0edAc6f37da032
entryPoint: 0x0000000071727De22E5E9d8BAf0edAc6f37da032,
smartAccount: 0x2c4d5B2d8B7ba9e15F09Da8fD455E312bF774Eeb,
rpcUrl: vm.envString("OPTIMISM_SEPOLIA_RPC")
});
}

function getLocalConfig() public returns (NetworkConfig memory) {
EntryPoint entryPoint = new EntryPoint();
MockAccount smartAccount = new MockAccount();

return NetworkConfig({
chainId: LOCAL_CHAIN_ID,
Expand All @@ -92,8 +99,9 @@ contract HelperConfig is Script {
inbox: address(0),
l2Oracle: address(0),
shoyuBashi: address(0),
deployerKey: 0,
entryPoint: address(entryPoint)
entryPoint: address(entryPoint),
smartAccount: address(smartAccount),
rpcUrl: ""
});
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/script/actions/SubmitRequest.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ contract SubmitRequest is Script, RRC7755Base {
(bytes32 destinationChain, bytes32 receiver, Call[] memory calls, bytes[] memory attributes) =
_initMessage(destinationChainId, duration);

vm.startBroadcast(config.deployerKey);
vm.startBroadcast();
outbox.sendMessage{value: 0.0002 ether}(destinationChain, receiver, abi.encode(calls), attributes);
vm.stopBroadcast();
}
Expand Down
3 changes: 1 addition & 2 deletions contracts/script/actions/SubmitToInbox.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ contract SubmitToInbox is Script, RRC7755Base {
bytes4 internal constant _SHOYU_BASHI_ATTRIBUTE_SELECTOR = 0xda07e15d; // shoyuBashi(bytes32)

function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
RRC7755Inbox inbox = RRC7755Inbox(payable(0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512));
address fulfiller = 0x23214A0864FC0014CAb6030267738F01AFfdd547;

(bytes32 sourceChain, bytes32 sender, bytes memory payload, bytes[] memory attributes) = _initMessage();

vm.startBroadcast(pk);
vm.startBroadcast();
inbox.fulfill(sourceChain, sender, payload, attributes, fulfiller);
vm.stopBroadcast();
}
Expand Down
111 changes: 111 additions & 0 deletions contracts/script/actions/SubmitUserOp.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {Script} from "forge-std/Script.sol";

import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol";
import {EntryPoint} from "account-abstraction/core/EntryPoint.sol";

import {GlobalTypes} from "../../src/libraries/GlobalTypes.sol";
import {RRC7755Base} from "../../src/RRC7755Base.sol";
import {RRC7755Outbox} from "../../src/RRC7755Outbox.sol";
import {HelperConfig} from "../HelperConfig.s.sol";

import {MockAccount} from "../../test/mocks/MockAccount.sol";

contract SubmitUserOp is Script, RRC7755Base {
using GlobalTypes for address;

bytes4 internal constant _REWARD_ATTRIBUTE_SELECTOR = 0xa362e5db; // reward(bytes32,uint256) rewardAsset, rewardAmount
bytes4 internal constant _DELAY_ATTRIBUTE_SELECTOR = 0x84f550e0; // delay(uint256,uint256) finalityDelaySeconds, expiry
bytes4 internal constant _L2_ORACLE_ATTRIBUTE_SELECTOR = 0x7ff7245a; // l2Oracle(address)
bytes4 internal constant _SHOYU_BASHI_ATTRIBUTE_SELECTOR = 0xda07e15d; // shoyuBashi(bytes32)
bytes4 internal constant _DESTINATION_CHAIN_SELECTOR = 0xdff49bf1; // destinationChain(bytes32)
bytes32 private constant _NATIVE_ASSET = 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee;

HelperConfig public helperConfig;

constructor() {
helperConfig = new HelperConfig();
}

function run() external {
HelperConfig.NetworkConfig memory config = helperConfig.getConfig(block.chainid);

address outboxAddr = config.opStackOutbox;
uint256 destinationChainId = helperConfig.BASE_SEPOLIA_CHAIN_ID();
uint256 duration = 1 weeks;

RRC7755Outbox outbox = RRC7755Outbox(outboxAddr);

(bytes32 destinationChain, bytes32 receiver, bytes memory payload) = _initMessage(destinationChainId, duration);

vm.createSelectFork(config.rpcUrl);

vm.startBroadcast();
outbox.sendMessage{value: 0.0002 ether}(destinationChain, receiver, payload, new bytes[](0));
vm.stopBroadcast();
}

function _initMessage(uint256 destinationChainId, uint256 duration)
private
returns (bytes32, bytes32, bytes memory)
{
HelperConfig.NetworkConfig memory dstConfig = helperConfig.getConfig(destinationChainId);
// HelperConfig.NetworkConfig memory srcConfig = helperConfig.getConfig(block.chainid);

address ethAddress = address(0);

uint128 verificationGasLimit = 100000;
uint128 callGasLimit = 100000;
uint128 maxPriorityFeePerGas = 100000;
uint128 maxFeePerGas = 100000;

vm.createSelectFork(dstConfig.rpcUrl);
uint256 nonce = EntryPoint(payable(dstConfig.entryPoint)).getNonce(dstConfig.smartAccount, 0);

bytes32 destinationChain = bytes32(destinationChainId);
bytes32 receiver = dstConfig.entryPoint.addressToBytes32();
bytes[] memory attributes = new bytes[](3);

attributes[0] = abi.encodeWithSelector(_REWARD_ATTRIBUTE_SELECTOR, _NATIVE_ASSET, 0.0002 ether);
attributes[1] = abi.encodeWithSelector(_DELAY_ATTRIBUTE_SELECTOR, duration, block.timestamp + 2 weeks);
attributes[2] = abi.encodeWithSelector(_L2_ORACLE_ATTRIBUTE_SELECTOR, dstConfig.l2Oracle);
// attributes[2] = abi.encodeWithSelector(_SHOYU_BASHI_ATTRIBUTE_SELECTOR, srcConfig.shoyuBashi);
// attributes[3] = abi.encodeWithSelector(_DESTINATION_CHAIN_SELECTOR, destinationChain);

PackedUserOperation memory userOp = PackedUserOperation({
sender: dstConfig.smartAccount,
nonce: nonce + 1,
initCode: "",
callData: abi.encodeWithSelector(MockAccount.executeUserOp.selector, address(dstConfig.inbox), ethAddress),
accountGasLimits: bytes32(abi.encodePacked(verificationGasLimit, callGasLimit)),
preVerificationGas: 100000,
gasFees: bytes32(abi.encodePacked(maxPriorityFeePerGas, maxFeePerGas)),
paymasterAndData: _encodePaymasterAndData(dstConfig.inbox, attributes, ethAddress),
signature: ""
});

return (destinationChain, receiver, abi.encode(userOp));
}

function _encodePaymasterAndData(address inbox, bytes[] memory attributes, address ethAddress)
private
pure
returns (bytes memory)
{
address precheck = address(0);
uint256 ethAmount = 0.0001 ether;
uint128 paymasterVerificationGasLimit = 100000;
uint128 paymasterPostOpGasLimit = 100000;
return abi.encodePacked(
inbox,
paymasterVerificationGasLimit,
paymasterPostOpGasLimit,
abi.encode(ethAddress, ethAmount, precheck, attributes)
);
}

// Including to block from coverage report
function test() external {}
}
6 changes: 1 addition & 5 deletions contracts/src/RRC7755Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ contract RRC7755Base {
/// @notice The selector for the precheck attribute
bytes4 internal constant _PRECHECK_ATTRIBUTE_SELECTOR = 0xbef86027; // precheck(bytes32)

/// @notice The selector for the isUserOp attribute. Used to designate a request designated to be a destination
/// chain ERC-4337 User Operation
bytes4 internal constant _USER_OP_ATTRIBUTE_SELECTOR = 0xd45448dd; // isUserOp(bool)

/// @notice This error is thrown if an attribute is not found in the attributes array
///
/// @param selector The selector of the attribute that was not found
Expand All @@ -48,7 +44,7 @@ contract RRC7755Base {
bytes32 receiver,
bytes calldata payload,
bytes[] calldata attributes
) public pure returns (bytes32) {
) public view virtual returns (bytes32) {
return keccak256(abi.encode(sourceChain, sender, destinationChain, receiver, payload, attributes));
}

Expand Down
6 changes: 2 additions & 4 deletions contracts/src/RRC7755Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,11 @@ contract RRC7755Inbox is RRC7755Base, Paymaster {
}

function _processAttributes(bytes[] calldata attributes) private pure returns (bool, address) {
bool isUserOp;
bool isUserOp = attributes.length == 0;
bytes32 precheckContract;

for (uint256 i; i < attributes.length; i++) {
if (bytes4(attributes[i]) == _USER_OP_ATTRIBUTE_SELECTOR) {
isUserOp = abi.decode(attributes[i][4:], (bool));
} else if (bytes4(attributes[i]) == _PRECHECK_ATTRIBUTE_SELECTOR) {
if (bytes4(attributes[i]) == _PRECHECK_ATTRIBUTE_SELECTOR) {
precheckContract = abi.decode(attributes[i][4:], (bytes32));
}
}
Expand Down
Loading

0 comments on commit e952c9d

Please sign in to comment.