Skip to content

Commit

Permalink
Add contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
MiloTruck committed Sep 3, 2024
1 parent d0def9a commit a45ec56
Show file tree
Hide file tree
Showing 11 changed files with 430 additions and 43 deletions.
6 changes: 1 addition & 5 deletions script/Counter.s.sol → script/Example.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";

contract CounterScript is Script {
Counter public counter;
contract ExampleScript is Script {

function setUp() public {}

function run() public {
vm.startBroadcast();

counter = new Counter();

vm.stopBroadcast();
}
}
14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

125 changes: 125 additions & 0 deletions src/amm/ConstantSumPair.sol
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);
}
}
6 changes: 6 additions & 0 deletions src/amm/interfaces/ICallbacks.sol
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;
}
22 changes: 22 additions & 0 deletions src/interfaces/IERC20.sol
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);
}
65 changes: 65 additions & 0 deletions src/tokens/ERC20.sol
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);
}
}
38 changes: 38 additions & 0 deletions src/utils/FixedPointMathLib.sol
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;
}
}
94 changes: 94 additions & 0 deletions src/utils/SafeTransferLib.sol
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
);
}
}
Loading

0 comments on commit a45ec56

Please sign in to comment.