Skip to content

Instantly share code, notes, and snippets.

@akolotov
Created October 9, 2023 04:27
Show Gist options
  • Save akolotov/9e70b43a75426c8276c061d9176b0968 to your computer and use it in GitHub Desktop.
Save akolotov/9e70b43a75426c8276c061d9176b0968 to your computer and use it in GitHub Desktop.
VerifySafeTx_Gov44.t.sol
// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.8.15;
import "forge-std/Test.sol";
import "forge-std/interfaces/IERC20.sol";
interface IZkBobPool {
function direct_deposit_queue() external view returns (address);
function denominator() external view returns (uint256);
}
interface ISafe {
function getOwners() external view returns (address[] memory);
function getThreshold() external view returns (uint256);
function approveHash(bytes32 hashToApprove) external;
function execTransaction(
address to,
uint256 value,
bytes calldata data,
uint8 operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
)
external
payable
returns (bool success);
}
contract SafeHelpers is Test {
struct SafeTxAPI {
bytes data;
uint256 nonce;
uint8 operation;
address safe;
uint256 safeTxGas;
bytes32 safeTxHash;
address to;
uint256 value;
}
function getSortedOwners(address safe) internal view returns (address[] memory owners) {
owners = ISafe(safe).getOwners();
// bubble sort owners
for (uint256 i = 0; i < owners.length; i++) {
for (uint256 j = i + 1; j < owners.length; j++) {
if (owners[i] > owners[j]) {
address t = owners[i];
owners[i] = owners[j];
owners[j] = t;
}
}
}
}
function execSafeTx(string memory safeChain, string memory safeTxHash) internal {
SafeTxAPI memory safeTx = getSafeTx(safeChain, safeTxHash);
address[] memory owners = getSortedOwners(safeTx.safe);
uint256 threshold = ISafe(safeTx.safe).getThreshold();
bytes memory signatures;
for (uint256 i = 0; i < threshold; i++) {
vm.prank(owners[i]);
ISafe(safeTx.safe).approveHash(safeTx.safeTxHash);
signatures = abi.encodePacked(signatures, uint96(0), owners[i], uint256(0), uint8(1));
}
ISafe(safeTx.safe).execTransaction({
to: safeTx.to,
value: safeTx.value,
data: safeTx.data,
operation: safeTx.operation,
safeTxGas: safeTx.safeTxGas,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: payable(address(0)),
signatures: signatures
});
}
function getSafeTx(string memory safeChain, string memory safeTxHash) internal returns (SafeTxAPI memory) {
bytes memory cmd = abi.encodePacked(
"curl -s https://safe-transaction-",
safeChain,
".safe.global/api/v1/multisig-transactions/",
safeTxHash,
"/ -s | jq -cM '{safe,to,value,data,operation,safeTxGas,nonce,safeTxHash} | .value |= tonumber'"
);
string[] memory inputs = new string[](3);
inputs[0] = "bash";
inputs[1] = "-c";
inputs[2] = string(cmd);
string memory rawJson = string(vm.ffi(inputs));
return abi.decode(vm.parseJson(rawJson), (SafeTxAPI));
}
}
contract VerifySafeTx_Gov44 is Test, SafeHelpers {
address constant pool = address(0x1CA8C2B9B20E18e86d5b9a72370fC6c91814c97C);
address constant usdc = address(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85);
address constant bob = address(0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B);
function forkOptimism() internal {
vm.createSelectFork("https://rpc.ankr.com/optimism", 110_612_000);
}
function testOptimism() public {
forkOptimism();
address dd_queue = IZkBobPool(pool).direct_deposit_queue();
uint256 bobToUSDCshift = 10 ** (IERC20(bob).decimals() - IERC20(usdc).decimals());
assertEq(IERC20(bob).balanceOf(dd_queue), 0);
uint256 bob_balance = IERC20(bob).balanceOf(pool);
execSafeTx("optimism", "0xb5625c2a40311a5a3a727b1a1f81202465f7f3dbcb13ffb41f5398d924d7c6a9");
execSafeTx("optimism", "0x7f9f061bc17ba96faa79ee40a44ab31f693470384c9b1c2985a874dc1370c997");
assertEq(IZkBobPool(pool).denominator(), (1 << 255) | 1000);
uint256 usdc_balance = IERC20(usdc).balanceOf(pool);
assertEq(bob_balance / bobToUSDCshift, usdc_balance);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment