Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
30eeb68
Revert "Abandon Katana chain" (#557)
jparklev May 12, 2026
b0f5378
Katana: delegate to base dispatch and add SushiSwapV3 fork
jparklev May 12, 2026
2f3b0ec
Katana: integration fork test for SushiV3 vbETH/AUSD swap
jparklev May 12, 2026
107c05b
Katana: wire CI integration env + fix stale test comment
jparklev May 12, 2026
1d9a424
Katana: add UNISWAPV3_VIP + slippage revert tests
jparklev May 12, 2026
70c8f60
Simplify Katana SushiV3 changes
jparklev May 12, 2026
d7e8ca2
Add script to redeploy Settlers and reclaim Safe ownership on revived…
jparklev May 19, 2026
9c81b49
revive_chain.sh: drop hardcoded --isolate
jparklev May 20, 2026
09a4c80
Bug! EIP-7825 gas-budget check was a no-op
jparklev May 21, 2026
d8279e9
Extract SafeMultisend script base
jparklev May 21, 2026
546d281
RedeploySettlers: split MultiSend per Safe; allow keyless simulation
jparklev May 21, 2026
08c8093
revive_chain.sh: bc-based balance floor
jparklev May 21, 2026
49dad0c
Katana SushiV3 test: align EVM version with chain_config, use vm.getC…
jparklev May 21, 2026
ccaa774
revive_chain.sh: derive balance floor from sim
jparklev May 22, 2026
54b6169
Address review: EraVm-aware codehash, per-deploy txs, require solvers…
jparklev May 26, 2026
f03592b
revive_chain.sh: derive per-sender balance floor from dry-run sim
jparklev May 26, 2026
ab978ad
Address review: hoist eraVmCompat to SafeMultisend, guard against sil…
jparklev May 26, 2026
c0adf8d
revive_chain.sh: fix balance-floor double-multiplier + partial-parse …
jparklev May 26, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
BNB_MAINNET_RPC_URL: ${{ secrets.BNB_MAINNET_RPC_URL }}
PLASMA_MAINNET_RPC_URL: ${{ secrets.PLASMA_MAINNET_RPC_URL }}
KATANA_MAINNET_RPC_URL: ${{ secrets.KATANA_MAINNET_RPC_URL }}
Comment thread
duncancmt marked this conversation as resolved.
ARBITRUM_MAINNET_RPC_URL: ${{ secrets.ARBITRUM_MAINNET_RPC_URL }}
BASE_MAINNET_RPC_URL: ${{ secrets.BASE_MAINNET_RPC_URL }}
MONAD_MAINNET_RPC_URL: ${{ secrets.MONAD_MAINNET_RPC_URL }}
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
### Breaking changes

* SolidlyV3 UniV3 fork removed from Mainnet and Sonic
* Abandon Katana chain (which only had stripped-down Settler anyways)
* `BRIDGE_TO_CCIP` modified to remove the `token` argument that is already included in `ccipSendData`
* `BRIDGE_TO_LAYER_ZERO_OFT` modified to remove the `nativeFee` argument that is already included in `sendData`
* `BRIDGE_ERC20_TO_MAYAN` and `BRIDGE_NATIVE_TO_MAYAN` modified to remove `forwarder` argument. It is now hardcoded.
Expand All @@ -14,6 +13,8 @@

### Non-breaking changes

* Add SushiSwapV3 UniV3 fork on Katana.

## 2026-04-10

### Breaking changes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ your integration.
* `0x0000000000001fF3684f28c67538d4D072C22734` on chains supporting the Cancun
hardfork (Ethereum mainnet, Ethereum Sepolia testnet, Polygon, Base, Optimism,
Arbitrum, Bnb, World Chain, Fantom Sonic, Ink, Avalanche, Unichain, Berachain,
Scroll, HyperEvm, Plasma, Monad mainnet, Abstract, Linea, Tempo)
Scroll, HyperEvm, Katana, Plasma, Monad mainnet, Abstract, Linea, Tempo)
* `0x0000000000005E88410CcDFaDe4a5EfaE4b49562` on chains supporting the Shanghai
hardfork (Mantle)

Expand Down
4 changes: 4 additions & 0 deletions api_secrets.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@
"etherscanKey": "",
"rpcUrl": ""
},
"katana": {
"etherscanKey": "",
"rpcUrl": ""
},
"plasma": {
"etherscanKey": "verifyContract",
"rpcUrl": ""
Expand Down
37 changes: 37 additions & 0 deletions chain_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,43 @@
"blockscoutApi": "https://www.hyperscan.com/api",
"etherscanApi": "https://api.etherscan.io/v2/api?chainid=999"
},
"katana": {
"chainId": 747474,
"displayName": "Katana",
"wnative": "0xEE7D8BCFb72bC1880D0Cf19822eB0A2e6577aB62",
"hardfork": {
"shanghai": true,
"cancun": true,
"osaka": false,
Comment thread
duncancmt marked this conversation as resolved.
"eraVm": false
},
"extraFlags": "--legacy",
"extraScriptFlags": "--isolate",
"gasMultiplierPercent": 200,
"minGasPriceGwei": 1,
"safe": {
"toehold": "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7",
"singleton": "0xfb1bffC9d739B8D520DaF37dF666da4C687191EA",
"factory": "0xC22834581EbC8527d974F8a1c97E1bEA4EF910BC",
"fallback": "0x017062a1dE2FE6b99BE3d9d37841FeD19F573804",
"multiCall": "0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B",
"apiUrl": "https://api.safe.global/tx-service/katana/api"
},
"governance": {
"upgradeSafe": "0xf36b9f50E59870A24F42F9Ba43b2aD0A4b8f2F51",
"deploymentSafe": "0x8E5DE7118a596E99B0563D3022039c11927f4827",
"pause": "0x1CeC01DC0fFEE5eB5aF47DbEc1809F2A7c601C30",
"daoSafe": "0x23030a6124E871F4744Cb9bc14D519b1f033FFe3"
},
"deployment": {
"allowanceHolder": "0x0000000000001fF3684f28c67538d4D072C22734",
"deployer": "0x00000000000004533Fe15556B1E086BB1A72cEae",
"forwardingMultiCall": "0x00000000000000CF9E3c5A26621af382fA17f24f",
"crossChainFactory": "0x00000000000000304861c3aDfb80dd5ebeC96325"
},
"etherscanApi": "https://api.etherscan.io/v2/api?chainid=747474",
"blockscoutApi": "https://explorer.katanarpc.com/api"
},
"plasma": {
"chainId": 9745,
"displayName": "Plasma",
Expand Down
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ mainnet = "${MAINNET_RPC_URL}"
bnb = "${BNB_MAINNET_RPC_URL}"
plasma = "${PLASMA_MAINNET_RPC_URL}"
arbitrum = "${ARBITRUM_MAINNET_RPC_URL}"
katana = "${KATANA_MAINNET_RPC_URL}"
base = "${BASE_MAINNET_RPC_URL}"
monad = "${MONAD_MAINNET_RPC_URL}"

Expand Down
227 changes: 7 additions & 220 deletions script/DeploySafes.s.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {Script} from "@forge-std/Script.sol";
import {Vm, VmSafe} from "@forge-std/Vm.sol";
import {SafeMultisend, ISafeExecute, ISafeFactory, ISafeOwners} from "./SafeMultisend.sol";
import {AddressDerivation} from "src/utils/AddressDerivation.sol";
import {Create3} from "src/utils/Create3.sol";
import {ZeroExSettlerDeployerSafeModule} from "src/deployer/SafeModule.sol";
Expand All @@ -11,13 +10,6 @@ import {ERC1967UUPSProxy} from "src/proxy/ERC1967UUPSProxy.sol";
import {SafeConfig} from "./SafeConfig.sol";
import {SafeBytecodes} from "./SafeCode.sol";

interface ISafeFactory {
function createProxyWithNonce(address singleton, bytes calldata initializer, uint256 saltNonce)
external
returns (address);
function proxyCreationCode() external view returns (bytes memory);
}

interface ISafeSetup {
function setup(
address[] calldata owners,
Expand All @@ -31,62 +23,17 @@ interface ISafeSetup {
) external;
}

interface ISafeExecute {
enum Operation {
Call,
DelegateCall
}

function execTransaction(
address to,
uint256 value,
bytes calldata data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
bytes calldata signatures
) external payable returns (bool);
}

interface ISafeOwners {
function addOwnerWithThreshold(address owner, uint256 _threshold) external;
function removeOwner(address prevOwner, address owner, uint256 _threshold) external;
function changeThreshold(uint256 _threshold) external;
function getOwners() external view returns (address[] memory);
}

interface ISafeModule {
function enableModule(address module) external;
}

interface ISafeMulticall {
/// @dev Sends multiple transactions and reverts all if one fails.
/// @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of
/// operation has to be uint8(0) in this version (=> 1 byte),
/// to as a address (=> 20 bytes),
/// value as a uint256 (=> 32 bytes),
/// data length as a uint256 (=> 32 bytes),
/// data as bytes.
/// see abi.encodePacked for more information on packed encoding
/// @notice The code is for most part the same as the normal MultiSend (to keep compatibility),
/// but reverts if a transaction tries to use a delegatecall.
/// @notice This method is payable as delegatecalls keep the msg.value from the previous call
/// If the calling method (e.g. execTransaction) received ETH this would revert otherwise
function multiSend(bytes memory transactions) external payable;
}

contract DeploySafes is Script {
contract DeploySafes is SafeMultisend {
bytes32 internal constant singletonHash = 0x21842597390c4c6e3c1239e434a682b054bd9548eee5e9b1d6a4482731023c0f;
bytes32 internal constant singletonHashEraVm = 0xe2ca068330339d608367d83a0b25545efe39e619098597699ab8ff828cb1ddd8;
bytes32 internal constant factoryHash = 0x337d7f54be11b6ed55fef7b667ea5488db53db8320a05d1146aa4bd169a39a9b;
bytes32 internal constant factoryHashEraVm = 0x55daa5d390d283edbc5fa835bd53befce45179c758feaac8c149a95850d0a6b6;
bytes32 internal constant fallbackHash = 0x03e69f7ce809e81687c69b19a7d7cca45b6d551ffdec73d9bb87178476de1abf;
bytes32 internal constant fallbackHashEraVm = 0x017e9a83d5513f503fb85274f4d1ad1811040d7caa31772750ffb08638c28fbb;
bytes32 internal constant multicallHash = 0xa9865ac2d9c7a1591619b188c4d88167b50df6cc0c5327fcbd1c8c75f7c066ad;
bytes32 internal constant multicallHashEraVm = 0x064ddbf252714bcd4cb79f679e8c12df96d998ce07bbb13b3118c1dbf4a31942;
uint256 internal constant safeDeploymentSaltNonce = 0;

// This is derived from calling `proxyCreationCode()` on the factory and then decoding the EraVm-style encoded
Expand All @@ -107,153 +54,6 @@ contract DeploySafes is Script {
SafeBytecodes safeBytecodes;
}

function _encodeMultisend(bytes[] memory calls) internal view returns (bytes memory result) {
// The Gnosis multicall contract uses a very obnoxious packed encoding
// that is very similar to, but not exactly the same as
// `abi.encodePacked`
assembly ("memory-safe") {
result := mload(0x40)
mstore(add(0x04, result), 0x8d80ff0a) // selector for `multiSend(bytes)`
mstore(add(0x24, result), 0x20)
let bytes_length_ptr := add(0x44, result)
mstore(bytes_length_ptr, 0x00)
for {
let i := add(0x20, calls)
let end := add(i, shl(0x05, mload(calls)))
let dst := add(0x20, bytes_length_ptr)
} lt(i, end) { i := add(0x20, i) } {
let src := mload(i)
let len := mload(src)
src := add(0x20, src)

// We're using the old identity precompile version instead of
// the MCOPY opcode version because I don't want to have to deal
// with maintaining two versions of this
if or(xor(returndatasize(), len), iszero(staticcall(gas(), 0x04, src, len, dst, len))) {
invalid()
}

dst := add(dst, len)
mstore(bytes_length_ptr, add(len, mload(bytes_length_ptr)))
}
mstore(result, add(0x44, mload(bytes_length_ptr)))
mstore(0x40, add(0x20, add(mload(result), result)))
}
}

function _encodeMultisend(address safe, bytes memory call) internal pure returns (bytes memory) {
return abi.encodePacked(
uint8(ISafeExecute.Operation.Call),
safe,
uint256(0), // value
call.length,
call
);
}

function _encodeChangeOwners(address safe, uint256 threshold, address oldOwner, address[] memory newOwners)
internal
view
returns (bytes[] memory)
{
bytes[] memory subCalls = new bytes[](newOwners.length + 1);
for (uint256 i; i < newOwners.length; i++) {
bytes memory data =
abi.encodeCall(ISafeOwners.addOwnerWithThreshold, (newOwners[newOwners.length - i - 1], 1));
subCalls[i] = _encodeMultisend(safe, data);
}
{
bytes memory data =
abi.encodeCall(ISafeOwners.removeOwner, (newOwners[newOwners.length - 1], oldOwner, threshold));
subCalls[newOwners.length] = _encodeMultisend(safe, data);
}
return subCalls;
}

modifier eraVmCompat(
bool isEraVm,
uint256 privateKey,
ISafeExecute safe,
ISafeFactory safeFactory,
address safeSingleton,
address safeFallback,
address safeMulticall,
SafeBytecodes memory safeBytecodes
) {
if (isEraVm) {
(VmSafe.CallerMode callerMode, address msgSender, address txOrigin) = vm.readCallers();
require(callerMode != VmSafe.CallerMode.Broadcast);
if (callerMode == VmSafe.CallerMode.RecurrentBroadcast) {
require(msgSender == txOrigin);
require(msgSender == vm.addr(privateKey));
vm.stopBroadcast();
}

bytes memory oldFactoryCode = address(safeFactory).code;
vm.etch(address(safeFactory), safeBytecodes.factoryCode);
bytes memory oldSingletonCode = safeSingleton.code;
vm.etch(safeSingleton, safeBytecodes.singletonCode);
bytes memory oldFallbackCode = safeFallback.code;
vm.etch(safeFallback, safeBytecodes.fallbackCode);
bytes memory oldMulticallCode = safeMulticall.code;
vm.etch(safeMulticall, safeBytecodes.multicallCode);

bytes memory oldSafeCode;
if (address(safe) != address(0)) {
oldSafeCode = address(safe).code;
vm.etch(address(safe), safeBytecodes.proxyCode);
}

vm.startPrank(msgSender, txOrigin);
vm.startStateDiffRecording();
_;
uint256 gasUsed = vm.lastCallGas().gasTotalUsed;
Vm.AccountAccess[] memory accesses = vm.stopAndReturnStateDiff();
vm.stopPrank();
gasUsed = gasUsed * 6 / 5;

Vm.AccountAccess memory theOneImportantCall;
for (uint256 i; i < accesses.length; i++) {
theOneImportantCall = accesses[i];
if (theOneImportantCall.kind == VmSafe.AccountAccessKind.Call) {
require(theOneImportantCall.accessor == msgSender, "unexpected top-level call");
for (uint256 j = i + 1; j < accesses.length; j++) {
Vm.AccountAccess memory jAA = accesses[j];
if (jAA.kind == VmSafe.AccountAccessKind.Call) {
require(jAA.accessor != msgSender || jAA.account == address(vm), "duplicate top-level call");
}
}
break;
}
}

vm.etch(address(safeFactory), oldFactoryCode);
vm.etch(safeSingleton, oldSingletonCode);
vm.etch(safeFallback, oldFallbackCode);
vm.etch(safeMulticall, oldMulticallCode);

if (address(safe) != address(0)) {
vm.etch(address(safe), oldSafeCode);
}

if (callerMode == VmSafe.CallerMode.RecurrentBroadcast) {
vm.startBroadcast(privateKey);

// repeat the call from the modified function, blindly, while broadcasting
{
address target = theOneImportantCall.account;
uint256 value = theOneImportantCall.value;
bytes memory data = theOneImportantCall.data;
assembly ("memory-safe") {
pop(call(gasUsed, target, value, add(0x20, data), mload(data), 0x00, 0x00))
}
}
}
} else {
_;
}
}

function _createProxyWithNonce(SafeCompatConfig memory compatConfig, bytes memory initializer, uint256 saltNonce)
private
eraVmCompat(
Expand Down Expand Up @@ -405,10 +205,7 @@ contract DeploySafes is Script {
safeFallback.codehash == (safeCompatConfig.isEraVm ? fallbackHashEraVm : fallbackHash),
"Safe fallback codehash"
);
require(
safeMulticall.codehash == (safeCompatConfig.isEraVm ? multicallHashEraVm : multicallHash),
"Safe multicall codehash"
);
_assertMulticallCodehash(safeMulticall);

require(Feature.unwrap(takerSubmittedFeature) == 2, "wrong taker-submitted feature (tokenId)");
require(Feature.unwrap(metaTxFeature) == 3, "wrong metatransaction feature (tokenId)");
Expand Down Expand Up @@ -619,14 +416,9 @@ contract DeploySafes is Script {
deploySetupCalls[3] = _encodeMultisend(deployerProxy, intentDeployCall);
deploySetupCalls[4] = _encodeMultisend(deployerProxy, bridgeDeployCall);
{
address prevSolver = 0x0000000000000000000000000000000000000001;
for (uint256 i; i < solvers.length; i++) {
address solver = solvers[i];
deploySetupCalls[i + 5] = _encodeMultisend(
predictedIntentSettler,
abi.encodeWithSignature("setSolver(address,address,bool)", prevSolver, solver, true)
);
prevSolver = solver;
bytes[] memory solverCalls = _encodeSolversMultisend(predictedIntentSettler, solvers);
for (uint256 i; i < solverCalls.length; i++) {
deploySetupCalls[i + 5] = solverCalls[i];
}
}
for (uint256 i; i < changeOwnersCalls.length; i++) {
Expand Down Expand Up @@ -719,12 +511,7 @@ contract DeploySafes is Script {
gasSplits[10] = gasleft();
_stopBroadcast(safeCompatConfig);

{
uint256 gasPrev = gasSplits[0];
for (uint256 i = 1; i < gasSplits.length; i++) {
require(gasPrev + 15728639 > (gasPrev = gasSplits[i]), "transaction is likely to exceed EIP-7825 limit");
}
}
_assertEip7825(gasSplits);

require(deployedModule == iceColdCoffee, "deployment/prediction mismatch");
require(deployedDeploymentSafe == deploymentSafe, "deployed safe/predicted safe mismatch");
Expand Down
Loading
Loading