-
-
Save k1rill-fedoseev/fd38a2ec10dfaf0e2608acfaea27505b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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