Skip to content

Commit ee628dc

Browse files
Add support for batches (#11)
Add support for batches.
2 parents 8f2687a + 5873def commit ee628dc

20 files changed

+1413
-96
lines changed

.env.example

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ LOCAL_DEPLOYER_ADDRESS= # Address of the deployer
99

1010
# GatewayWallet Configuration
1111
GATEWAYWALLET_OWNER_ADDRESS= # Address with owner role
12+
GATEWAYWALLET_DEPLOYER_ADDRESS= # Address that deploys new implementations
1213
GATEWAYWALLET_PAUSER_ADDRESS= # Address with pauser role
1314
GATEWAYWALLET_DENYLISTER_ADDRESS= # Address with denylister role
1415
GATEWAYWALLET_SUPPORTED_TOKEN_1= # Address of one of the supported token addresses
1516
GATEWAYWALLET_DOMAIN= # Chain-specific domain identifier (uint32)
1617
GATEWAYWALLET_WITHDRAWAL_DELAY= # Withdrawal delay in blocks (uint256)
1718
GATEWAYWALLET_BURNSIGNER_ADDRESS= # Address with burnSigner role
1819
GATEWAYWALLET_FEERECIPIENT_ADDRESS= # Address to receive fees
19-
GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS= # Address to allow contract signers
20+
GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS= # Address to allow contract signers (required for xreserve upgrade)
21+
GATEWAYWALLET_BATCHSIGNER_ADDRESS= # Address with batchSigner role (required for batches-support upgrade)
2022

2123
# GatewayMinter Configuration
2224
GATEWAYMINTER_OWNER_ADDRESS= # Address with owner role
@@ -27,8 +29,8 @@ GATEWAYMINTER_DOMAIN= # Chain-specific domain id
2729
GATEWAYMINTER_ATTESTATION_SIGNER= # Address for attestation signing
2830
GATEWAYMINTER_TOKEN_AUTH_1= # First token mint authority (leave blank for none)
2931

30-
# Deployed Contract Validation
31-
GATEWAYMINTER_IMPL_ADDRESS= # Address of the GateWayMinter implementation address
32-
GATEWAYWALLET_IMPL_ADDRESS= # Address of the GateWayWallet implementation address
32+
# Deployed Contract Validation / Upgrade Scripts
33+
GATEWAYMINTER_IMPL_ADDRESS= # Address of the GatewayMinter implementation address
34+
GATEWAYWALLET_IMPL_ADDRESS= # Address of the GatewayWallet implementation address
3335
GATEWAYWALLET_MINTER_ADDRESS= # Address of the GatewayMinter Proxy contract
3436
GATEWAYMINTER_WALLET_ADDRESS= # Address of the GatewayWallet Proxy contract

foundry.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ optimizer = true
1111
optimizer_runs = 150
1212
gas_reports = ["GatewayWallet", "GatewayMinter", "UpgradeablePlaceholder"]
1313
fs_permissions = [{ access = "read", path = "./script/compiled-contract-artifacts"}]
14+
via_ir = true
1415

1516
[rpc_endpoints]
1617
ethereum = "https://ethereum-rpc.publicnode.com"

script/001_DeployGatewayWallet.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,20 @@ contract DeployGatewayWallet is BaseBytecodeDeployScript {
4848
address gatewayWalletFeeRecipient = vm.envAddress("GATEWAYWALLET_FEERECIPIENT_ADDRESS");
4949
address gatewayWalletContractSignersAllowlister =
5050
vm.envAddress("GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS");
51+
address gatewayWalletBatchSigner = vm.envAddress("GATEWAYWALLET_BATCHSIGNER_ADDRESS");
5152

5253
// Encode initialization call with all parameters
5354
return abi.encodeWithSignature(
54-
"initialize(address,address,address[],uint32,uint256,address,address,address)",
55+
"initialize(address,address,address[],uint32,uint256,address,address,address,address)",
5556
gatewayWalletPauser,
5657
gatewayWalletDenylister,
5758
supportedTokens,
5859
domain,
5960
withdrawalDelay,
6061
gatewayWalletBurnSigner,
6162
gatewayWalletFeeRecipient,
62-
gatewayWalletContractSignersAllowlister
63+
gatewayWalletContractSignersAllowlister,
64+
gatewayWalletBatchSigner
6365
);
6466
}
6567

script/004_UpgradeGatewayWallet.sol

Lines changed: 198 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,86 +18,228 @@
1818
pragma solidity ^0.8.29;
1919

2020
import {console} from "forge-std/console.sol";
21-
import {EnvSelector, EnvConfig} from "script/000_Constants.sol";
22-
import {BaseBytecodeDeployScript} from "script/BaseBytecodeDeployScript.sol";
21+
import {Script} from "forge-std/Script.sol";
2322

24-
/// @title UpgradeGatewayWallet
25-
/// @notice Upgrade script for GatewayWallet implementation
26-
/// @dev Deploys a new implementation and upgrades the proxy to use it
27-
contract UpgradeGatewayWallet is BaseBytecodeDeployScript {
28-
/// @dev Environment selector for multi-environment deployment
29-
EnvSelector private envSelector;
30-
31-
constructor() {
32-
envSelector = new EnvSelector();
33-
}
23+
import {GatewayWallet} from "src/GatewayWallet.sol";
3424

25+
/// @title UpgradeGatewayWallet
26+
/// @notice Generic upgrade script for GatewayWallet - always upgrades to the latest implementation
27+
/// @dev This script upgrades an existing GatewayWallet proxy to the latest implementation.
28+
/// Uses bytecode deployment from compiled artifacts to ensure consistent bytecode across chains.
29+
///
30+
/// Upgrade process:
31+
/// 1. Deploy new GatewayWallet implementation from compiled bytecode
32+
/// 2. Call upgradeToAndCall on the proxy to upgrade to new implementation
33+
/// 3. (Optional) Update contract signers allowlister if GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS is set
34+
/// 4. (Optional) Add batch signer if GATEWAYWALLET_BATCHSIGNER_ADDRESS is set
35+
/// 5. Validate deployed state
36+
///
37+
/// Required environment variables:
38+
/// - GATEWAYMINTER_WALLET_ADDRESS: The proxy address to upgrade
39+
/// - GATEWAYWALLET_OWNER_ADDRESS: The owner who can upgrade and set roles
40+
/// - GATEWAYWALLET_DEPLOYER_ADDRESS: The address that deploys the new implementation
41+
///
42+
/// Optional environment variables (set if needed):
43+
/// - GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS: Address for contract signers allowlister role
44+
/// - GATEWAYWALLET_BATCHSIGNER_ADDRESS: Address to add as batch signer
45+
///
46+
/// Usage:
47+
/// # Dry run (simulation)
48+
/// forge script script/004_UpgradeGatewayWallet.sol --rpc-url $RPC_URL -vvvv
49+
///
50+
/// # Broadcast to network
51+
/// forge script script/004_UpgradeGatewayWallet.sol --rpc-url $RPC_URL --broadcast -vvvv \
52+
/// --account deployer --account owner
53+
contract UpgradeGatewayWallet is Script {
3554
/// @notice Main upgrade function that deploys new implementation and upgrades the proxy
36-
/// @dev Upgrade process:
37-
/// 1. Deploy new GatewayWallet implementation
38-
/// 2. Call upgradeToAndCall on the proxy to upgrade to new implementation
39-
/// 3. Update contract signers allowlister
55+
/// @return newImplAddress The address of the newly deployed implementation
4056
function run() public returns (address newImplAddress) {
41-
// Get the proxy address from environment variable
57+
// Required environment variables
4258
address gatewayWalletProxy = vm.envAddress("GATEWAYMINTER_WALLET_ADDRESS");
43-
44-
// Get the owner address who can perform upgrades
4559
address gatewayWalletOwner = vm.envAddress("GATEWAYWALLET_OWNER_ADDRESS");
60+
address deployer = vm.envAddress("GATEWAYWALLET_DEPLOYER_ADDRESS");
4661

47-
// Get environment configuration
48-
EnvConfig memory config = envSelector.getEnvironmentConfig();
62+
// Optional environment variables (default to 0x0 if not set)
63+
address contractSignersAllowlister = vm.envOr("GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS", address(0));
64+
address batchSigner = vm.envOr("GATEWAYWALLET_BATCHSIGNER_ADDRESS", address(0));
4965

50-
// Use environment-specific values
51-
address deployer = config.deployerAddress;
52-
address factory = config.factoryAddress;
53-
54-
// Use the same salt as the original deployment
55-
// CREATE2 will automatically generate a different address because the bytecode has changed
56-
bytes32 newWalletImplSalt = config.walletSalt;
57-
58-
// First, validate that the owner is properly configured
66+
// Validation
67+
require(gatewayWalletProxy != address(0), "GATEWAYMINTER_WALLET_ADDRESS not set");
5968
require(gatewayWalletOwner != address(0), "GATEWAYWALLET_OWNER_ADDRESS not set");
69+
require(deployer != address(0), "GATEWAYWALLET_DEPLOYER_ADDRESS not set");
6070

61-
// Step 1: Deploy new GatewayWallet implementation (using deployer)
71+
console.log("\n=== GatewayWallet Upgrade Configuration ===");
72+
console.log("Proxy address:", gatewayWalletProxy);
73+
console.log("Owner address:", gatewayWalletOwner);
74+
console.log("Deployer address:", deployer);
75+
if (contractSignersAllowlister != address(0)) {
76+
console.log("Contract Signers Allowlister:", contractSignersAllowlister);
77+
} else {
78+
console.log("Contract Signers Allowlister: (not set, skipping)");
79+
}
80+
if (batchSigner != address(0)) {
81+
console.log("Batch Signer:", batchSigner);
82+
} else {
83+
console.log("Batch Signer: (not set, skipping)");
84+
}
85+
86+
// Log current state before upgrade
87+
console.log("\n=== Pre-Upgrade State ===");
88+
_logCurrentState(gatewayWalletProxy);
89+
90+
// Step 1: Deploy new GatewayWallet implementation from bytecode
91+
console.log("\n=== Step 1: Deploy new implementation ===");
6292
vm.startBroadcast(deployer);
63-
newImplAddress = deploy(factory, "GatewayWallet.json", newWalletImplSalt, hex"");
64-
console.log("New GatewayWallet implementation address", newImplAddress);
93+
newImplAddress = _deployFromBytecode("GatewayWallet.json");
94+
console.log("New GatewayWallet implementation address:", newImplAddress);
6595
vm.stopBroadcast();
6696

67-
// Step 2: Upgrade the proxy to the new implementation (using owner)
97+
// Step 2: Upgrade the proxy to the new implementation
98+
console.log("\n=== Step 2: Upgrade proxy to new implementation ===");
6899
vm.startBroadcast(gatewayWalletOwner);
69100

70-
// In OpenZeppelin v5, we must use upgradeToAndCall even for simple upgrades
71-
// Pass empty data since we don't need to call any initialization function
72101
bytes memory upgradeCallData = abi.encodeWithSignature("upgradeToAndCall(address,bytes)", newImplAddress, hex"");
73-
74-
// Execute the upgrade through the proxy
75-
(bool success, bytes memory returnData) = gatewayWalletProxy.call(upgradeCallData);
76-
require(success, string(abi.encodePacked("Upgrade failed: ", returnData)));
102+
(bool upgradeSuccess, bytes memory upgradeReturnData) = gatewayWalletProxy.call(upgradeCallData);
103+
require(upgradeSuccess, string(abi.encodePacked("Upgrade failed: ", upgradeReturnData)));
77104

78105
console.log("Successfully upgraded GatewayWallet proxy to new implementation");
79-
console.log("Proxy address:", gatewayWalletProxy);
80-
console.log("New implementation address:", newImplAddress);
81-
82106
vm.stopBroadcast();
83107

84-
// Step 3: Update contract signers allowlister
85-
address contractSignersAllowlister = vm.envAddress("GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS");
86-
require(contractSignersAllowlister != address(0), "GATEWAYWALLET_CONTRACT_SIGNERS_ALLOWLISTER_ADDRESS not set");
108+
// Step 3: Update contract signers allowlister (optional)
109+
if (contractSignersAllowlister != address(0)) {
110+
console.log("\n=== Step 3: Update contract signers allowlister ===");
111+
vm.startBroadcast(gatewayWalletOwner);
112+
113+
bytes memory updateAllowlisterCallData =
114+
abi.encodeWithSignature("updateContractSignersAllowlister(address)", contractSignersAllowlister);
115+
(bool allowlisterSuccess, bytes memory allowlisterReturnData) =
116+
gatewayWalletProxy.call(updateAllowlisterCallData);
117+
require(
118+
allowlisterSuccess,
119+
string(abi.encodePacked("updateContractSignersAllowlister failed: ", allowlisterReturnData))
120+
);
121+
122+
console.log("Successfully updated contract signers allowlister to:", contractSignersAllowlister);
123+
vm.stopBroadcast();
124+
} else {
125+
console.log("\n=== Step 3: Update contract signers allowlister (skipped - not set) ===");
126+
}
127+
128+
// Step 4: Add batch signer (optional)
129+
if (batchSigner != address(0)) {
130+
console.log("\n=== Step 4: Add batch signer ===");
131+
vm.startBroadcast(gatewayWalletOwner);
132+
133+
bytes memory addBatchSignerCallData = abi.encodeWithSignature("addBatchSigner(address)", batchSigner);
134+
(bool batchSignerSuccess, bytes memory batchSignerReturnData) =
135+
gatewayWalletProxy.call(addBatchSignerCallData);
136+
require(batchSignerSuccess, string(abi.encodePacked("addBatchSigner failed: ", batchSignerReturnData)));
137+
138+
console.log("Successfully added batch signer:", batchSigner);
139+
vm.stopBroadcast();
140+
} else {
141+
console.log("\n=== Step 4: Add batch signer (skipped - not set) ===");
142+
}
143+
144+
// Step 5: Validate deployed state
145+
console.log("\n=== Step 5: Validate post-upgrade state ===");
146+
_validateUpgrade(
147+
gatewayWalletProxy, newImplAddress, gatewayWalletOwner, contractSignersAllowlister, batchSigner
148+
);
149+
150+
// Summary
151+
console.log("\n=== GatewayWallet Upgrade Complete ===");
152+
console.log("Proxy address:", gatewayWalletProxy);
153+
console.log("New implementation:", newImplAddress);
154+
if (contractSignersAllowlister != address(0)) {
155+
console.log("Contract signers allowlister:", contractSignersAllowlister);
156+
}
157+
if (batchSigner != address(0)) {
158+
console.log("Batch signer:", batchSigner);
159+
}
160+
}
87161

88-
console.log("\nUpdating contract signers allowlister...");
89-
console.log("New allowlister address:", contractSignersAllowlister);
162+
/// @notice Deploys a contract from compiled bytecode artifact using CREATE
163+
/// @dev Reads bytecode from JSON artifact and deploys using inline assembly
164+
/// @param contractFileName Name of the contract's compiled artifact file (e.g., "GatewayWallet.json")
165+
/// @return addr The deployed contract address
166+
function _deployFromBytecode(string memory contractFileName) internal returns (address addr) {
167+
// Get project root directory and construct path to compiled contract artifact
168+
string memory root = vm.projectRoot();
169+
string memory path = string.concat(root, "/script/compiled-contract-artifacts/", contractFileName);
170+
string memory json = vm.readFile(path);
171+
172+
// Extract bytecode from the compiled contract artifact
173+
bytes memory bytecode = abi.decode(vm.parseJson(json, ".bytecode.object"), (bytes));
174+
175+
// Deploy using CREATE opcode (not CREATE2)
176+
assembly {
177+
addr := create(0, add(bytecode, 0x20), mload(bytecode))
178+
}
179+
require(addr != address(0), "Deployment failed");
180+
}
90181

91-
// Start broadcast for the allowlister update
92-
vm.startBroadcast(gatewayWalletOwner);
182+
/// @notice Logs the current state of the GatewayWallet before upgrade
183+
function _logCurrentState(address proxyAddress) internal view {
184+
GatewayWallet wallet = GatewayWallet(proxyAddress);
93185

94-
bytes memory updateAllowlisterCallData =
95-
abi.encodeWithSignature("updateContractSignersAllowlister(address)", contractSignersAllowlister);
96-
(bool updateSuccess, bytes memory updateReturnData) = gatewayWalletProxy.call(updateAllowlisterCallData);
97-
require(updateSuccess, string(abi.encodePacked("updateContractSignersAllowlister failed: ", updateReturnData)));
186+
console.log("Current owner:", wallet.owner());
187+
console.log("Current paused:", wallet.paused());
188+
console.log("Current domain:", wallet.domain());
98189

99-
console.log("Successfully updated contract signers allowlister");
190+
// Get current implementation from ERC1967 slot
191+
bytes32 implSlot = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
192+
bytes32 implBytes = vm.load(proxyAddress, implSlot);
193+
address currentImpl = address(uint160(uint256(implBytes)));
194+
console.log("Current implementation:", currentImpl);
195+
}
100196

101-
vm.stopBroadcast();
197+
/// @notice Validates the GatewayWallet state after upgrade
198+
function _validateUpgrade(
199+
address proxyAddress,
200+
address expectedImpl,
201+
address expectedOwner,
202+
address expectedAllowlister,
203+
address expectedBatchSigner
204+
) internal view {
205+
GatewayWallet wallet = GatewayWallet(proxyAddress);
206+
207+
// Validate implementation was updated
208+
bytes32 implSlot = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
209+
bytes32 implBytes = vm.load(proxyAddress, implSlot);
210+
address actualImpl = address(uint160(uint256(implBytes)));
211+
require(actualImpl == expectedImpl, "Implementation address mismatch");
212+
console.log("[OK] Implementation updated to:", actualImpl);
213+
214+
// Validate owner is unchanged
215+
address actualOwner = wallet.owner();
216+
require(actualOwner == expectedOwner, "Owner changed unexpectedly");
217+
console.log("[OK] Owner unchanged:", actualOwner);
218+
219+
// Validate contract is not paused
220+
bool paused = wallet.paused();
221+
require(!paused, "Contract should not be paused after upgrade");
222+
console.log("[OK] Contract not paused");
223+
224+
// Validate contract signers allowlister (if it was set)
225+
if (expectedAllowlister != address(0)) {
226+
address actualAllowlister = wallet.contractSignersAllowlister();
227+
require(actualAllowlister == expectedAllowlister, "Contract signers allowlister mismatch");
228+
console.log("[OK] Contract signers allowlister set to:", actualAllowlister);
229+
}
230+
231+
// Validate batch signer was added (if it was set)
232+
if (expectedBatchSigner != address(0)) {
233+
bool isBatchSigner = wallet.isBatchSigner(expectedBatchSigner);
234+
require(isBatchSigner, "Batch signer not registered");
235+
console.log("[OK] Batch signer registered:", expectedBatchSigner);
236+
}
237+
238+
// Validate domain is still set
239+
uint32 domain = wallet.domain();
240+
require(domain != 0, "Domain should be set");
241+
console.log("[OK] Domain:", domain);
242+
243+
console.log("\nAll validations passed!");
102244
}
103245
}

0 commit comments

Comments
 (0)