Skip to content

Instantly share code, notes, and snippets.

@k1rill-fedoseev
Created September 6, 2023 14:35
Show Gist options
  • Save k1rill-fedoseev/fd38a2ec10dfaf0e2608acfaea27505b to your computer and use it in GitHub Desktop.
Save k1rill-fedoseev/fd38a2ec10dfaf0e2608acfaea27505b 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);
}
interface IBobSwap {
struct Collateral {
uint128 balance; // accounted required collateral balance
uint128 buffer; // buffer of tokens that should not be invested and kept as is
uint96 dust; // small non-withdrawable yield to account for possible rounding issues
address yield; // address of yield-generating implementation
uint128 price; // X tokens / 1 bob
uint64 inFee; // fee for TOKEN->BOB buys
uint64 outFee; // fee for BOB->TOKEN sells
uint256 maxBalance; // limit on the amount of the specific collateral
uint256 maxInvested; // limit on the amount of the specific collateral subject to investment
}
function collateral(address token) external view returns (Collateral memory);
function sell(address token, uint256 amount) external returns (uint256);
}
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 forkOP() internal {
vm.createSelectFork("https://rpc.ankr.com/optimism", 109_173_000);
}
function forkMainnet() internal {
vm.createSelectFork("https://rpc.ankr.com/eth", 18_077_000);
}
function testOP() public {
address bobSwap = address(0x8aEb89D5C689C2cf373Fe8b56c7A0cD5BDc74CE6);
address usdc = address(0x7F5c764cBc14f9669B88837ca1490cCa17c31607);
address usdt = address(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58);
forkOP();
execSafeTx("optimism", "0x46de9e2a6f458aced523ca5f2228da3c3bb904cb156df1d53d2de7a25c53137a");
assertApproxEqAbs(IERC20(bob).totalSupply(), 1_000_000 ether, 250_000 ether);
// redeem remaining BobSwap collateral
uint256 usdcCollateral = IBobSwap(bobSwap).collateral(usdc).balance;
uint256 usdtCollateral = IBobSwap(bobSwap).collateral(usdt).balance;
deal(bob, user1, 1_000_000 ether);
vm.startPrank(user1);
IERC20(bob).approve(bobSwap, type(uint256).max);
IBobSwap(bobSwap).sell(usdc, usdcCollateral * 1e12);
IBobSwap(bobSwap).sell(usdt, usdtCollateral * 1e12);
vm.stopPrank();
assertEq(IERC20(usdc).balanceOf(bobSwap), 0);
assertEq(IERC20(usdt).balanceOf(bobSwap), 0);
execSafeTx("optimism", "0xdd2eb7c027d081e537a73366bd0da2b4c6ca09794ce4a087ebd334fc4b517b66");
assertApproxEqAbs(IERC20(bob).totalSupply(), 1_000_000 ether, 100 ether);
}
function testMainnet() public {
address bobSwap = address(0x15729Ac1795Fa02448a55D206005dC1914144a9F);
address usdc = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
address usdt = address(0xdAC17F958D2ee523a2206206994597C13D831ec7);
forkMainnet();
execSafeTx("mainnet", "0xe63c6ba5bf639c7c4d08089e34ba042a5d4ff62836036bf2e39d001cbf0aeec1");
assertApproxEqAbs(IERC20(bob).totalSupply(), 1_000_000 ether, 200_000 ether);
// redeem remaining BobSwap collateral
uint256 usdcCollateral = IBobSwap(bobSwap).collateral(usdc).balance;
uint256 usdtCollateral = IBobSwap(bobSwap).collateral(usdt).balance;
deal(bob, user1, 1_000_000 ether);
vm.startPrank(user1);
IERC20(bob).approve(bobSwap, type(uint256).max);
IBobSwap(bobSwap).sell(usdc, usdcCollateral * 1e12);
IBobSwap(bobSwap).sell(usdt, usdtCollateral * 1e12);
vm.stopPrank();
assertEq(IERC20(usdc).balanceOf(bobSwap), 0);
assertEq(IERC20(usdt).balanceOf(bobSwap), 0);
execSafeTx("mainnet", "0x02210007521a89e948b0988cab39b0c8c72ecbb7f49180b58fc9fb0dbe6f66ce");
assertApproxEqAbs(IERC20(bob).totalSupply(), 1_000_000 ether, 100 ether);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment