diff --git a/README.md b/README.md index 01c6523..5074d76 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# smart-contracts-core +# contract-libs The collections of smart contracts that power the Axie Land Delegation. diff --git a/foundry.toml b/foundry.toml index 596d69b..9082196 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,12 +6,6 @@ ffi = true # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options -remappings = [ - 'forge-std/=lib/forge-std/src/', - 'ds-test/=lib/forge-std/lib/ds-test/src/', - '@openzeppelin/=lib/openzeppelin-contracts/', -] - solc = '0.8.23' evm_version = 'istanbul' use_literal_content = true diff --git a/lib/forge-std b/lib/forge-std index bdea49f..2f11269 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit bdea49f9bb3c58c8c35850c3bdc17eaeea756e9a +Subproject commit 2f112697506eab12d433a65fdc31a639548fe365 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 932fddf..bd325d5 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 932fddf69a699a9a80fd2396fd1a2ab91cdda123 +Subproject commit bd325d56b4c62c9c5c1aff048c37c6bb18ac0290 diff --git a/src/LibErrorHandler.sol b/src/LibErrorHandler.sol new file mode 100644 index 0000000..8202fca --- /dev/null +++ b/src/LibErrorHandler.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +library LibErrorHandler { + /// @dev Reserves error definition to upload to signature database. + error ExternalCallFailed(bytes4 msgSig, bytes4 callSig); + + /// @notice handle low level call revert if call failed, + /// If extcall return empty bytes, reverts with custom error. + /// @param status Status of external call + /// @param callSig function signature of the calldata + /// @param returnOrRevertData bytes result from external call + function handleRevert(bool status, bytes4 callSig, bytes memory returnOrRevertData) internal pure { + // Get the function signature of current context + bytes4 msgSig = msg.sig; + assembly ("memory-safe") { + if iszero(status) { + // Load the length of bytes array + let revertLength := mload(returnOrRevertData) + // Check if length != 0 => revert following reason from external call + if iszero(iszero(revertLength)) { + // Start of revert data bytes. The 0x20 offset is always the same. + revert(add(returnOrRevertData, 0x20), revertLength) + } + + // Load free memory pointer + let ptr := mload(0x40) + // Store 4 bytes the function selector of ExternalCallFailed(msg.sig, callSig) + // Equivalent to revert ExternalCallFailed(bytes4,bytes4) + mstore(ptr, 0x49bf4104) + // Store 4 bytes of msgSig parameter in the next slot + mstore(add(ptr, 0x20), msgSig) + // Store 4 bytes of callSig parameter in the next slot + mstore(add(ptr, 0x40), callSig) + // Revert 68 bytes of error starting from 0x1c + revert(add(ptr, 0x1c), 0x44) + } + } + } +} diff --git a/src/math/LibSafeRange.sol b/src/math/LibSafeRange.sol new file mode 100644 index 0000000..402411c --- /dev/null +++ b/src/math/LibSafeRange.sol @@ -0,0 +1,20 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library LibSafeRange { + function add(uint256 a, uint256 b) internal pure returns (uint256 c) { + unchecked { + c = a + b; + if (c < a) return type(uint256).max; + } + } + + /** + * @dev Returns value of a + b; in case result is larger than upperbound, upperbound is returned. + */ + function addWithUpperbound(uint256 a, uint256 b, uint256 ceil) internal pure returns (uint256 c) { + if (a > ceil || b > ceil) return ceil; + c = add(a, b); + if (c > ceil) return ceil; + } +} diff --git a/src/transfers/LibNativeTransfer.sol b/src/transfers/LibNativeTransfer.sol new file mode 100644 index 0000000..0a0dee3 --- /dev/null +++ b/src/transfers/LibNativeTransfer.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { LibErrorHandler } from "../LibErrorHandler.sol"; + +/** + * @title NativeTransferHelper + */ +library LibNativeTransfer { + using LibErrorHandler for bool; + + /** + * @dev Transfers Native Coin and wraps result for the method caller to a recipient. + */ + function transfer(address to, uint256 value, uint256 gasAmount) internal { + (bool success, bytes memory returnOrRevertData) = trySendValue(to, value, gasAmount); + success.handleRevert(bytes4(0x0), returnOrRevertData); + } + + /** + * @dev Unsafe send `amount` Native to the address `to`. If the sender's balance is insufficient, + * the call does not revert. + * + * Note: + * - Does not assert whether the balance of sender is sufficient. + * - Does not assert whether the recipient accepts NATIVE. + * - Consider using `ReentrancyGuard` before calling this function. + * + */ + function trySendValue(address to, uint256 value, uint256 gasAmount) + internal + returns (bool success, bytes memory returnOrRevertData) + { + (success, returnOrRevertData) = to.call{ value: value, gas: gasAmount }(""); + } +} diff --git a/test/transfers/LibNativeTransfer.t.sol b/test/transfers/LibNativeTransfer.t.sol new file mode 100644 index 0000000..5eddad0 --- /dev/null +++ b/test/transfers/LibNativeTransfer.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import { Test } from "forge-std/Test.sol"; +import { LibNativeTransfer } from "src/transfers/LibNativeTransfer.sol"; + +contract LibNativeTransferTest is Test { + function testFork_RevertWhen_TransferNativeToContractWithoutFallback_safeTransfer( + address any, + uint256 amount, + uint256 gas + ) external { + vm.deal(any, amount); + vm.expectRevert(); + vm.prank(any); + LibNativeTransfer.transfer(address(this), amount, gas); + } + + function testConcrete_TransferNative(uint256 gas) external { + LibNativeTransfer.transfer(address(0xBEEF), 1e18, gas); + assertEq(address(0xBEEF).balance, 1e18); + } + + function testFork_TransferNativeToRecipient(address recipient, uint256 amount, uint256 gas) external { + // Transferring to msg.sender can fail because it's possible to overflow their ETH balance as it begins non-zero. + if (recipient.code.length > 0 || uint256(uint160(recipient)) <= 18 || recipient == msg.sender) return; + + amount = _bound(amount, 0, address(this).balance); + LibNativeTransfer.transfer(recipient, amount, gas); + + assertEq(recipient.balance, amount); + } +}