Skip to content

Instantly share code, notes, and snippets.

@k1rill-fedoseev
Created October 17, 2023 08:48
Show Gist options
  • Save k1rill-fedoseev/4e3ad34b58df31ae34a6a306fc2f01ac to your computer and use it in GitHub Desktop.
Save k1rill-fedoseev/4e3ad34b58df31ae34a6a306fc2f01ac to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.8.15;
import "forge-std/Test.sol";
import "forge-std/interfaces/IERC20.sol";
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_Gov41 is Test, SafeHelpers {
address constant bob = address(0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B);
address constant user1 = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266);
function forkARB() internal {
vm.createSelectFork("https://rpc.ankr.com/arbitrum", 141_334_400);
}
function forkBNB() internal {
vm.createSelectFork("https://rpc.ankr.com/bsc", 32_677_150);
}
function forkMainnet() internal {
vm.createSelectFork("https://rpc.ankr.com/eth", 18_368_900);
}
function forkOP() internal {
vm.createSelectFork("https://rpc.ankr.com/optimism", 110_966_400);
}
function forkPolygon() internal {
vm.createSelectFork("https://rpc.ankr.com/polygon", 48_818_450);
}
function testARB() public {
forkARB();
execSafeTx("arbitrum", "0xdbe514ab2d21d0428bb42e89755d1ed43d705ad9d67684ae4eb78282c634711e");
assertApproxEqAbs(IERC20(bob).totalSupply(), 10_000 ether, 1 ether);
}
function testBNB() public {
forkBNB();
execSafeTx("bsc", "0x0da4a9906438ed3c616c0dafa5722bf0dd34b876b110a0c4a03ab91f56fe3918");
assertApproxEqAbs(IERC20(bob).totalSupply(), 10_000 ether, 100 ether);
}
function testMainnet() public {
forkMainnet();
execSafeTx("mainnet", "0x6598e5c898ebafdd7430ac1490d16442060985e1fdf14ca49b131e3f8d55c602");
assertApproxEqAbs(IERC20(bob).totalSupply(), 20_000 ether, 100 ether);
}
function testOP() public {
forkOP();
execSafeTx("optimism", "0xde713e31812e929ec9d250b5c75d2055ba94f6b134fad4e94e8bcc2cac25dec3");
assertApproxEqAbs(IERC20(bob).totalSupply(), 100_000 ether, 100 ether);
}
function testPolygon() public {
forkPolygon();
execSafeTx("polygon", "0x6c1172b196533945c02db26d2c32da10192a4a602eb99107831efd7f495beb3f");
assertApproxEqAbs(IERC20(bob).totalSupply(), 200_000 ether, 10_000 ether);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment