@@ -1176,4 +1176,136 @@ contract SafeTransferLibTest is SoladyTest {
11761176 function totalSupplyQuery (address token ) public view returns (uint256 ) {
11771177 return SafeTransferLib.totalSupply (token);
11781178 }
1179+
1180+ function testSaveMoveETHViaVault (bytes32 ) public {
1181+ address to = _randomUniqueHashedAddress ();
1182+ assertEq (to.balance, 0 );
1183+
1184+ uint256 amount0 = _bound (_random (), 0 , 2 ** 128 - 1 );
1185+ uint256 amount1 = _bound (_random (), 0 , 2 ** 128 - 1 );
1186+ vm.deal (address (this ), 2 ** 160 - 1 );
1187+ address vault = this .safeMoveETH (to, amount0);
1188+ assertEq (vault.balance, amount0);
1189+ assertEq (this .safeMoveETH (to, amount1), vault);
1190+ assertEq (vault.balance, amount0 + amount1);
1191+
1192+ address pranker = _randomUniqueHashedAddress ();
1193+ vm.prank (pranker);
1194+ (bool success ,) = vault.call ("" );
1195+ require (success);
1196+ assertEq (vault.balance, amount0 + amount1);
1197+ assertEq (to.balance, 0 );
1198+
1199+ vm.prank (to);
1200+ (success,) = vault.call ("" );
1201+ require (success);
1202+ assertEq (vault.balance, 0 );
1203+ assertEq (to.balance, amount0 + amount1);
1204+ }
1205+
1206+ function safeMoveETHViaMover (bytes32 ) public {
1207+ _deployETHMover ();
1208+
1209+ address to = _randomHashedAddress ();
1210+ assertEq (to.balance, 0 );
1211+
1212+ uint256 amount0 = _bound (_random (), 0 , 2 ** 128 - 1 );
1213+ uint256 amount1 = _bound (_random (), 0 , 2 ** 128 - 1 );
1214+ vm.deal (address (this ), 2 ** 160 - 1 );
1215+ uint256 selfBalanceBefore = address (this ).balance;
1216+ assertEq (SafeTransferLib.safeMoveETH (to, amount0), address (0 ));
1217+
1218+ assertEq (to.balance, amount0);
1219+ assertEq (address (this ).balance, selfBalanceBefore - amount0);
1220+
1221+ if (SafeTransferLib.ETH_MOVER.code.length == 0 ) {
1222+ address vault = this .safeMoveETH (to, amount0);
1223+ assertEq (vault.balance, amount1);
1224+ assertEq (to.balance, amount0);
1225+ assertEq (address (this ).balance, selfBalanceBefore - amount0);
1226+ } else {
1227+ assertEq (this .safeMoveETH (to, amount0), address (0 ));
1228+ assertEq (to.balance, amount0 + amount1);
1229+ assertEq (address (this ).balance, selfBalanceBefore - amount0 - amount1);
1230+ }
1231+ }
1232+
1233+ function testSaveMoveETHToSelfIsNoOp (bytes32 ) public {
1234+ if (_randomChance (2 )) _deployETHMover ();
1235+ address to = address (this );
1236+ uint256 amount = _bound (_random (), 0 , 2 ** 128 - 1 );
1237+ vm.deal (address (this ), 2 ** 160 - 1 );
1238+ uint256 selfBalanceBefore = address (this ).balance;
1239+ assertEq (this .safeMoveETH (to, amount), address (0 ));
1240+ assertEq (address (this ).balance, selfBalanceBefore);
1241+ }
1242+
1243+ function testSaveMoveETHToMoverReverts (bytes32 ) public {
1244+ if (_randomChance (2 )) _deployETHMover ();
1245+ address to = SafeTransferLib.ETH_MOVER;
1246+
1247+ uint256 amount = _bound (_random (), 0 , 2 ** 128 - 1 );
1248+ vm.deal (address (this ), 2 ** 160 - 1 );
1249+
1250+ vm.expectRevert (SafeTransferLib.ETHTransferFailed.selector );
1251+ this .safeMoveETH (to, amount);
1252+ }
1253+
1254+ function testSaveMoveETHInsufficientBalanceReverts (bytes32 ) public {
1255+ if (_randomChance (2 )) _deployETHMover ();
1256+ address to = _randomHashedAddress ();
1257+
1258+ uint256 amount = _bound (_random (), 0 , 2 ** 128 - 1 );
1259+ vm.deal (address (this ), 2 ** 128 - 1 );
1260+
1261+ if (address (this ).balance < amount) {
1262+ vm.expectRevert (SafeTransferLib.ETHTransferFailed.selector );
1263+ this .safeMoveETH (to, amount);
1264+ } else {
1265+ this .safeMoveETH (to, amount);
1266+ }
1267+ }
1268+
1269+ function safeMoveETH (address to , uint256 amount ) public returns (address ) {
1270+ return SafeTransferLib.safeMoveETH (_brutalized (to), amount);
1271+ }
1272+
1273+ function _deployETHMover () internal {
1274+ bytes memory initCode = hex "623d35ff3d526003601df3 " ;
1275+ bytes32 salt = 0x000000000000000000000000000000000000000063d76c4f57ebf10084429e18 ;
1276+ address mover = _nicksCreate2 (0 , salt, initCode);
1277+ assertEq (mover.code, hex "3d35ff " );
1278+ assertEq (mover, SafeTransferLib.ETH_MOVER);
1279+ }
1280+
1281+ function _deployOneTimeVault (address to , uint256 amount ) internal returns (address vault ) {
1282+ /// @solidity memory-safe-assembly
1283+ assembly {
1284+ to := shr (96 , shl (96 , to)) // Clean upper 96 bits.
1285+ for {} 1 {} {
1286+ let m := mload (0x40 )
1287+ // If the mover is missing or bricked, deploy a minimal accrual contract
1288+ // that withdraws all ETH to `to` when being called only by `to`.
1289+ mstore (
1290+ add (m, 0x1f ), 0x33146025575b600160005260206000f35b3d3d3d3d47335af1601a573d3dfd
1291+ )
1292+ mstore (m, or (to, shl (160 , 0x6034600b3d3960343df3fe73 )))
1293+ // Compute and store the bytecode hash.
1294+ mstore8 (0x00 , 0xff ) // Write the prefix.
1295+ mstore (0x35 , keccak256 (m, 0x3f ))
1296+ mstore (0x01 , shl (96 , address ())) // Deployer.
1297+ mstore (0x15 , 0 ) // Salt.
1298+ vault := keccak256 (0x00 , 0x55 )
1299+ if iszero (
1300+ mul (
1301+ returndatasize (),
1302+ call (gas (), vault, amount, codesize (), 0x00 , codesize (), 0x00 )
1303+ )
1304+ ) { if iszero (create2 (0 , m, 0x3f , 0 )) { revert (codesize (), codesize ()) } } // For gas estimation.
1305+
1306+ mstore (0x40 , m)
1307+ break
1308+ }
1309+ }
1310+ }
11791311}
0 commit comments