-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
430 additions
and
43 deletions.
There are no files selected for viewing
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
This file was deleted.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.20; | ||
|
||
import {IERC20} from "src/interfaces/IERC20.sol"; | ||
import {ERC20} from "src/tokens/ERC20.sol"; | ||
import {SafeTransferLib} from "src/utils/SafeTransferLib.sol"; | ||
import {FixedPointMathLib} from "src/utils/FixedPointMathLib.sol"; | ||
import {ICallbacks} from "./interfaces/ICallbacks.sol"; | ||
|
||
/// @title ConstantSumPair | ||
/// @notice A minimal x + y = k AMM. | ||
contract ConstantSumPair is ERC20 { | ||
using SafeTransferLib for IERC20; | ||
using FixedPointMathLib for uint256; | ||
|
||
address public immutable owner; | ||
|
||
IERC20 public immutable tokenX; | ||
IERC20 public immutable tokenY; | ||
|
||
uint256 public k; | ||
|
||
uint256 public price; | ||
|
||
// ======================================== CONSTRUCTOR ======================================== | ||
|
||
constructor() { | ||
owner = msg.sender; | ||
} | ||
|
||
// ======================================== MODIFIERS ======================================== | ||
|
||
/** | ||
* @notice Enforces the x + y = k invariant | ||
*/ | ||
modifier invariant() { | ||
_; | ||
|
||
require(_computeK() >= k, "K"); | ||
} | ||
|
||
// ======================================== PERMISSIONED FUNCTIONS ======================================== | ||
|
||
/** | ||
* @notice Set the price for the AMM. | ||
* | ||
* @param _price The price of tokenY in tokenX. Has 18 decimals. | ||
*/ | ||
function setPrice(uint256 _price) external { | ||
require(msg.sender == owner, "OWNER"); | ||
|
||
price = _price; | ||
k = _computeK(); | ||
} | ||
|
||
// ======================================== MUTATIVE FUNCTIONS ======================================== | ||
|
||
/** | ||
* @notice Add liquidity to the pair and mint LP tokens. | ||
* | ||
* @param deltaK The amount of liquidity added. | ||
*/ | ||
function addK(uint256 deltaK) external invariant returns (uint256 shares) { | ||
shares = k == 0 ? deltaK : deltaK.mulDivDown(totalSupply, k); | ||
|
||
k += deltaK; | ||
_mint(msg.sender, shares); | ||
} | ||
|
||
/** | ||
* @notice Remove liquidity form the pair and burn LP tokens. | ||
* | ||
* @param amountXOut The amount of tokenX to withdraw. | ||
* @param amountYOut The amount of tokenY to withdraw. | ||
* @param deltaK The amount of liquidity removed. | ||
*/ | ||
function removeK(uint256 amountXOut, uint256 amountYOut, uint256 deltaK) | ||
external | ||
invariant | ||
returns (uint256 shares) | ||
{ | ||
shares = deltaK.mulDivUp(totalSupply, k); | ||
|
||
k -= deltaK; | ||
_burn(msg.sender, shares); | ||
|
||
tokenX.safeTransfer(msg.sender, amountXOut); | ||
tokenY.safeTransfer(msg.sender, amountYOut); | ||
} | ||
|
||
/** | ||
* @notice Transfer tokens out from the pair. | ||
* | ||
* @param amountXOut The amount of tokenX to transfer out. | ||
* @param amountYOut The amount of tokenY to transfer out. | ||
* @param data Data passed to caller in the onTokensReceived callback. | ||
*/ | ||
function transferTokens(uint256 amountXOut, uint256 amountYOut, bytes calldata data) external invariant { | ||
if (amountXOut != 0) tokenX.safeTransfer(msg.sender, amountXOut); | ||
if (amountYOut != 0) tokenY.safeTransfer(msg.sender, amountYOut); | ||
|
||
if (data.length != 0) { | ||
ICallbacks(msg.sender).onTokensReceived(msg.sender, amountXOut, amountYOut, data); | ||
} | ||
} | ||
|
||
// ======================================== VIEW FUNCTIONS ======================================== | ||
|
||
function name() public pure override returns (string memory) { | ||
return "ConstantSumPairLiquidity"; | ||
} | ||
|
||
function symbol() public pure override returns (string memory) { | ||
return "CSPL"; | ||
} | ||
|
||
// ======================================== VIEW FUNCTIONS ======================================== | ||
|
||
function _computeK() internal view returns (uint256) { | ||
uint256 reserveX = tokenX.balanceOf(address(this)); | ||
uint256 reserveY = tokenY.balanceOf(address(this)); | ||
|
||
return reserveX + reserveY.divWadDown(price); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.20; | ||
|
||
interface ICallbacks { | ||
function onTokensReceived(address sender, uint256 amountXOut, uint256 amountYOut, bytes calldata data) external; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.20; | ||
|
||
/// @title IERC20 | ||
/// @notice Interface of the ERC-20 standard. | ||
interface IERC20 { | ||
event Transfer(address indexed from, address indexed to, uint256 value); | ||
|
||
event Approval(address indexed owner, address indexed spender, uint256 value); | ||
|
||
function totalSupply() external view returns (uint256); | ||
|
||
function balanceOf(address account) external view returns (uint256); | ||
|
||
function allowance(address owner, address spender) external view returns (uint256); | ||
|
||
function approve(address spender, uint256 value) external returns (bool); | ||
|
||
function transfer(address to, uint256 value) external returns (bool); | ||
|
||
function transferFrom(address from, address to, uint256 value) external returns (bool); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.20; | ||
|
||
/// @title ERC20 | ||
/// @notice A minimal ERC20 implementation. | ||
abstract contract ERC20 { | ||
event Transfer(address indexed from, address indexed to, uint256 amount); | ||
event Approval(address indexed owner, address indexed spender, uint256 amount); | ||
|
||
uint256 public totalSupply; | ||
|
||
mapping(address => uint256) public balanceOf; | ||
|
||
mapping(address => mapping(address => uint256)) public allowance; | ||
|
||
function name() public view virtual returns (string memory); | ||
|
||
function symbol() public view virtual returns (string memory); | ||
|
||
function decimals() public view virtual returns (uint8) { | ||
return 18; | ||
} | ||
|
||
function approve(address spender, uint256 amount) external returns (bool) { | ||
allowance[msg.sender][spender] = amount; | ||
|
||
emit Approval(msg.sender, spender, amount); | ||
|
||
return true; | ||
} | ||
|
||
function transfer(address to, uint256 amount) external returns (bool) { | ||
balanceOf[msg.sender] -= amount; | ||
balanceOf[to] += amount; | ||
|
||
emit Transfer(msg.sender, to, amount); | ||
|
||
return true; | ||
} | ||
|
||
function transferFrom(address from, address to, uint256 amount) public returns (bool) { | ||
allowance[from][msg.sender] -= amount; | ||
|
||
balanceOf[from] -= amount; | ||
balanceOf[to] += amount; | ||
|
||
emit Transfer(from, to, amount); | ||
|
||
return true; | ||
} | ||
|
||
function _mint(address to, uint256 amount) internal virtual { | ||
balanceOf[to] += amount; | ||
totalSupply += amount; | ||
|
||
emit Transfer(address(0), to, amount); | ||
} | ||
|
||
function _burn(address from, uint256 amount) internal virtual { | ||
balanceOf[from] -= amount; | ||
totalSupply -= amount; | ||
|
||
emit Transfer(from, address(0), amount); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.20; | ||
|
||
/// @title FixedPointMathLib | ||
/// @notice Library to manage fixed-point arithmetic. | ||
library FixedPointMathLib { | ||
uint256 constant WAD = 1e18; | ||
|
||
/// @dev Returns (x * y) / WAD rounded down. | ||
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { | ||
return mulDivDown(x, y, WAD); | ||
} | ||
|
||
/// @dev Returns (x * y) / WAD rounded up. | ||
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { | ||
return mulDivUp(x, y, WAD); | ||
} | ||
|
||
/// @dev Returns (x * WAD) / y rounded down. | ||
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { | ||
return mulDivDown(x, WAD, y); | ||
} | ||
|
||
/// @dev Returns (x * WAD) / y rounded up. | ||
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { | ||
return mulDivUp(x, WAD, y); | ||
} | ||
|
||
/// @dev Returns (x * y) / d rounded down. | ||
function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { | ||
return (x * y) / d; | ||
} | ||
|
||
/// @dev Returns (x * y) / d rounded up. | ||
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { | ||
return (x * y + (d - 1)) / d; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.20; | ||
|
||
import {IERC20} from "src/interfaces/IERC20.sol"; | ||
|
||
/// @title SafeTransferLib | ||
/// @notice Library for safe ETH and ERC20 transfers. | ||
library SafeTransferLib { | ||
error ETHTransferFailed(); | ||
error ERC20OperationFailed(); | ||
|
||
/** | ||
* @dev Send `amount` of ETH and returns whether the transfer succeeded. | ||
*/ | ||
function tryTransferETH(address to, uint256 amount) internal returns (bool success) { | ||
assembly { | ||
success := call(gas(), to, amount, 0, 0, 0, 0) | ||
} | ||
} | ||
|
||
/** | ||
* @dev Send `amount` of ETH and revert if the transfer failed. | ||
*/ | ||
function transferETH(address to, uint256 amount) internal { | ||
bool success = tryTransferETH(to, amount); | ||
if (!success) { | ||
revert ETHTransferFailed(); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Forcefully send `amount` of ETH to the recipient. | ||
*/ | ||
function forceTransferETH(address to, uint256 amount) internal { | ||
bool success = tryTransferETH(to, amount); | ||
|
||
// If the transfer with CALL fails, use SELFDESTRUCT to forcefully transfer ETH. | ||
if (!success) { | ||
assembly { | ||
mstore(0x00, to) // Store the address in scratch space. | ||
mstore8(0x0b, 0x73) // Opcode `PUSH20`. | ||
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. | ||
pop(create(amount, 0x0b, 0x16)) // Return value is not checked as CREATE should never revert. | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @dev Send `amount` of `token`. Revert if the transfer failed. | ||
*/ | ||
function safeTransfer(IERC20 token, address to, uint256 amount) internal { | ||
_callOptionalReturnWithRevert(token, abi.encodeCall(token.transfer, (to, amount))); | ||
} | ||
|
||
/** | ||
* @dev Transfer `amount` of `token` from `from` to `to`. Revert if the transfer failed. | ||
*/ | ||
function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal { | ||
_callOptionalReturnWithRevert(token, abi.encodeCall(token.transferFrom, (from, to, amount))); | ||
} | ||
|
||
/** | ||
* @dev Set an allowance for `token` of `amount`. Revert if the approval failed. | ||
* This does not work when called with `amount = 0` for tokens that revert on zero approval (eg. BNB). | ||
*/ | ||
function safeApprove(IERC20 token, address to, uint256 amount) internal { | ||
bytes memory approveData = abi.encodeCall(token.approve, (to, amount)); | ||
bool success = _callOptionalReturn(token, approveData); | ||
|
||
// If the original approval fails, call approve(to, 0) before re-trying. | ||
// For tokens that revert on non-zero to non-zero approval (eg. USDT). | ||
if (!success) { | ||
_callOptionalReturnWithRevert(token, abi.encodeCall(token.approve, (to, 0))); | ||
_callOptionalReturnWithRevert(token, approveData); | ||
} | ||
} | ||
|
||
function _callOptionalReturnWithRevert(IERC20 token, bytes memory data) internal { | ||
bool success = _callOptionalReturn(token, data); | ||
if (!success) { | ||
revert ERC20OperationFailed(); | ||
} | ||
} | ||
|
||
function _callOptionalReturn(IERC20 token, bytes memory data) internal returns (bool) { | ||
(bool success, bytes memory returndata) = address(token).call(data); | ||
|
||
return success && ( | ||
returndata.length == 0 | ||
? address(token).code.length != 0 // if returndata is empty, token must have code | ||
: abi.decode(returndata, (bool)) // if returndata is not empty, it must be true | ||
); | ||
} | ||
} |
Oops, something went wrong.