Skip to content

Commit

Permalink
Merge pull request #83 from base-org/jack/user-op-updates
Browse files Browse the repository at this point in the history
improve user op handling in outbox, remove private key from script ma…
  • Loading branch information
jackchuma authored Feb 10, 2025
2 parents a83556e + e952c9d commit 38d7dc2
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 38d7dc2

Please sign in to comment.