Skip to content

Commit

Permalink
Merge pull request #8 from Tenderize/nv/unsigned-only
Browse files Browse the repository at this point in the history
feat: use unsigned integers only
  • Loading branch information
kyriediculous authored Apr 12, 2024
2 parents 2c6c68f + b320893 commit a7360d0
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 62 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ Deployments and upgrades can through the `SwapFactory` contract for indexing pur

TenderSwap can also be deployed standlone following the same pattern as the `SwapFactory` contract.

`Factory::deploy` requires an implementation address. Each pool will have its own implementation contract where
`constants` or `immutables` can be specified for each pool. While this adds operational overhead and complexity for
potential upgrades, it significantly improves the gas cost of functions that use these parameters.

### Format

Format the contracts:
Expand Down
4 changes: 2 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[profile.default]
bytecode_hash = "none"
fuzz = { runs = 10_000 }
fuzz = { runs = 1_000 }
gas_reports = ["*"]
libs = ["lib"]
# optimizer = true (default)
optimizer_runs = 200
optimizer_runs = 100
fs_permissions = [{ access = "read-write", path = "./" }]
solc = "0.8.20"

Expand Down
9 changes: 5 additions & 4 deletions script/Swap_Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Script, console2 } from "forge-std/Script.sol";
import { ERC20 } from "solmate/tokens/ERC20.sol";
import { TenderSwap, ConstructorConfig } from "@tenderize/swap/Swap.sol";
import { SwapFactory } from "@tenderize/swap/Factory.sol";
import { SD59x18 } from "@prb/math/SD59x18.sol";
import { UD60x18 } from "@prb/math/UD60x18.sol";
import { ERC1967Proxy } from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol";

address constant FACTORY = address(0);
Expand All @@ -19,14 +19,15 @@ contract Swap_Deploy is Script {
// Start broadcasting with private key from `.env` file
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address underlying = vm.envAddress("UNDERLYING");
SD59x18 BASE_FEE = SD59x18.wrap(vm.envInt("BASE_FEE"));
SD59x18 K = SD59x18.wrap(vm.envInt("K"));
UD60x18 BASE_FEE = UD60x18.wrap(vm.envUint("BASE_FEE"));
UD60x18 K = UD60x18.wrap(vm.envUint("K"));

ConstructorConfig cfg = ConstructorConfig({ UNDERLYING: ERC20(underlying), BASE_FEE: BASE_FEE, K: K });

function run() public {
vm.startBroadcast(deployerPrivateKey);
(address proxy, address implementation) = SwapFactory(FACTORY).deploy(cfg);
address implementation = address(new TenderSwap{ salt: bytes32(uint256(1)) }(cfg));
(address proxy) = SwapFactory(FACTORY).deploy(implementation);
console2.log("Deployment for ", underlying);
console2.log("TenderSwap deployed at: ", proxy);
console2.log("Implementation deployed at: ", implementation);
Expand Down
22 changes: 11 additions & 11 deletions src/Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

pragma solidity ^0.8.20;

import { Owned } from "solmate/auth/Owned.sol";
import { ERC1967Proxy } from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { TenderSwap, ConstructorConfig } from "@tenderize/swap/Swap.sol";

Expand All @@ -22,8 +21,10 @@ import { UUPSUpgradeable } from "openzeppelin-contracts-upgradeable/proxy/utils/
// Used for subgraph indexing and atomic deployments

contract SwapFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
error UNDERLYING_MISMATCH();

event SwapDeployed(address underlying, address swap, address implementation);
event SwapUpgraded(address underlying, address swap, address implementation);
event SwapUpgraded(address underlying, address swap, address implementation, uint256 version);

mapping(address pool => uint256 v) public version;

Expand All @@ -36,30 +37,29 @@ contract SwapFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
_disableInitializers();
}

function deploy(ConstructorConfig memory cfg) external onlyOwner returns (address proxy, address implementation) {
function deploy(address implementation) external onlyOwner returns (address proxy) {
uint256 v = 1;
// Deploy the implementation
implementation = address(new TenderSwap{ salt: bytes32(v) }(cfg));
// deploy the contract
proxy = address(
new ERC1967Proxy{ salt: bytes32("tenderswap") }(implementation, abi.encodeWithSelector(TenderSwap.initialize.selector))
);

TenderSwap(proxy).transferOwnership(owner());
version[proxy] = v;
emit SwapDeployed(address(cfg.UNDERLYING), proxy, implementation);
emit SwapDeployed(address(TenderSwap(proxy).UNDERLYING()), proxy, implementation);
}

function upgrade(ConstructorConfig memory cfg, address swapProxy) external onlyOwner returns (address implementation) {
if (TenderSwap(swapProxy).UNDERLYING() != cfg.UNDERLYING) {
revert("SwapFactory: UNDERLYING_MISMATCH");
function upgrade(address newImplementation, address swapProxy) external onlyOwner returns (address implementation) {
address underlying = address(TenderSwap(swapProxy).UNDERLYING());
if (underlying != address(TenderSwap(newImplementation).UNDERLYING())) {
revert UNDERLYING_MISMATCH();
}

uint256 v = ++version[swapProxy];

implementation = address(new TenderSwap{ salt: bytes32(v) }(cfg));

TenderSwap(swapProxy).upgradeTo(implementation);

emit SwapUpgraded(underlying, swapProxy, implementation, v);
}

///@dev required by the OZ UUPS module
Expand Down
93 changes: 51 additions & 42 deletions src/Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
//
// Copyright (c) Tenderize Labs Ltd

import { SD59x18, ZERO as ZERO_SD59, UNIT, unwrap, sd } from "@prb/math/SD59x18.sol";
import { UD60x18, ZERO as ZERO_UD60, UNIT as UNIT_60x18, ud } from "@prb/math/UD60x18.sol";
import { UD60x18, ZERO as ZERO_UD60x18, UNIT as UNIT_60x18, ud } from "@prb/math/UD60x18.sol";
import { ERC20 } from "solmate/tokens/ERC20.sol";
import { ERC721 } from "solmate/tokens/ERC721.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
Expand Down Expand Up @@ -47,15 +46,15 @@ error ErrorCalculateLPShares();

struct ConstructorConfig {
ERC20 UNDERLYING;
SD59x18 BASE_FEE;
SD59x18 K;
UD60x18 BASE_FEE;
UD60x18 K;
}

struct SwapParams {
SD59x18 u;
SD59x18 U;
SD59x18 s;
SD59x18 S;
UD60x18 u;
UD60x18 U;
UD60x18 s;
UD60x18 S;
}

abstract contract SwapStorage {
Expand All @@ -68,7 +67,7 @@ abstract contract SwapStorage {
// total amount of liabilities owed to LPs
uint256 liabilities;
// sum of token supplies that have outstanding unlocks
SD59x18 S;
UD60x18 S;
// Recovery amount, if `recovery` > 0 enable recovery mode
uint256 recovery;
// treasury share of rewards pending withdrawal
Expand All @@ -78,7 +77,7 @@ abstract contract SwapStorage {
// amount unlocking per asset
mapping(address asset => uint256 unlocking) unlockingForAsset;
// last supply of a tenderizer when seen, tracked because they are rebasing tokens
mapping(address asset => SD59x18 lastSupply) lastSupplyForAsset;
mapping(address asset => UD60x18 lastSupply) lastSupplyForAsset;
// relayer fees
mapping(address relayer => uint256 reward) relayerRewards;
}
Expand Down Expand Up @@ -106,8 +105,8 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
event RelayerRewardsClaimed(address indexed relayer, uint256 rewards);

ERC20 public immutable UNDERLYING;
SD59x18 public immutable BASE_FEE;
SD59x18 public immutable K;
UD60x18 public immutable BASE_FEE;
UD60x18 public immutable K;

// Minimum cut of the fee for LPs when an unlock is bought
UD60x18 public constant MIN_LP_CUT = UD60x18.wrap(0.05e18);
Expand All @@ -131,7 +130,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
_disableInitializers();
}

function lpToken() public view returns (ERC20) {
function lpToken() external view returns (ERC20) {
Data storage $ = _loadStorageSlot();
return ERC20($.lpToken);
}
Expand All @@ -140,7 +139,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
* @notice Amount of liabilities outstanding to liquidity providers.
* Liabilities represent all the deposits from liquidity providers and their earned fees.
*/
function liabilities() public view returns (uint256) {
function liabilities() external view returns (uint256) {
Data storage $ = _loadStorageSlot();
return $.liabilities;
}
Expand All @@ -159,7 +158,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
*/
function utilisation() public view returns (UD60x18 r) {
Data storage $ = _loadStorageSlot();
if ($.liabilities == 0) return ZERO_UD60;
if ($.liabilities == 0) return ZERO_UD60x18;
r = _utilisation($.unlocking, $.liabilities);
}

Expand Down Expand Up @@ -244,7 +243,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
* @notice Claim outstanding rewards for a relayer.
* @return relayerReward Amount of tokens claimed
*/
function claimRelayerRewards() public returns (uint256 relayerReward) {
function claimRelayerRewards() external returns (uint256 relayerReward) {
Data storage $ = _loadStorageSlot();

relayerReward = $.relayerRewards[msg.sender];
Expand All @@ -256,7 +255,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
emit RelayerRewardsClaimed(msg.sender, relayerReward);
}

function claimTreasuryRewards() public onlyOwner returns (uint256 treasuryReward) {
function claimTreasuryRewards() external onlyOwner returns (uint256 treasuryReward) {
Data storage $ = _loadStorageSlot();

treasuryReward = $.treasuryRewards;
Expand Down Expand Up @@ -284,12 +283,12 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
* @return out Amount of output tokens
* @return fee Amount of fees paid
*/
function quote(address asset, uint256 amount) public view returns (uint256 out, uint256 fee) {
function quote(address asset, uint256 amount) external view returns (uint256 out, uint256 fee) {
Data storage $ = _loadStorageSlot();

SD59x18 U = sd(int256($.unlocking));
SD59x18 u = sd(int256($.unlockingForAsset[asset]));
(SD59x18 s, SD59x18 S) = _checkSupply(asset);
UD60x18 U = ud($.unlocking);
UD60x18 u = ud($.unlockingForAsset[asset]);
(UD60x18 s, UD60x18 S) = _checkSupply(asset);

SwapParams memory p = SwapParams({ U: U, u: u, S: S, s: s });
return _quote(amount, p);
Expand All @@ -310,10 +309,10 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS

Data storage $ = _loadStorageSlot();

SD59x18 U = sd(int256($.unlocking));
SD59x18 u = sd(int256($.unlockingForAsset[asset]));
SD59x18 x = sd(int256(amount));
(SD59x18 s, SD59x18 S) = _checkSupply(asset);
UD60x18 U = ud($.unlocking);
UD60x18 u = ud($.unlockingForAsset[asset]);
UD60x18 x = ud(amount);
(UD60x18 s, UD60x18 S) = _checkSupply(asset);

SwapParams memory p = SwapParams({ U: U, u: u, S: S, s: s });

Expand Down Expand Up @@ -402,7 +401,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
// - Update S if unlockingForAsset is now zero
if (ufa == 0) {
$.S = $.S.sub($.lastSupplyForAsset[tenderizer]);
$.lastSupplyForAsset[tenderizer] = ZERO_SD59;
$.lastSupplyForAsset[tenderizer] = ZERO_UD60x18;
}
// - Update unlockingForAsset
$.unlockingForAsset[tenderizer] = ufa;
Expand Down Expand Up @@ -481,7 +480,7 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
// - Update S if unlockingForAsset is now zero
if (ufa == 0) {
$.S = $.S.sub($.lastSupplyForAsset[tenderizer]);
$.lastSupplyForAsset[tenderizer] = ZERO_SD59;
$.lastSupplyForAsset[tenderizer] = ZERO_UD60x18;
}
// - Update unlockingForAsset
$.unlockingForAsset[tenderizer] = ufa;
Expand All @@ -506,31 +505,41 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
function _quote(uint256 amount, SwapParams memory p) internal view returns (uint256 out, uint256 fee) {
Data storage $ = _loadStorageSlot();

SD59x18 x = sd(int256(amount));
SD59x18 L = sd(int256($.liabilities));
SD59x18 nom;
SD59x18 denom;
UD60x18 x = ud((amount));
UD60x18 L = ud(($.liabilities));
UD60x18 nom;
UD60x18 denom;

// (((u + x)*k - U + u)*((U + x)/L)**k + (-k*u + U - u)*(U/L)**k)*(S + U)/(k*(1 + k)*(s + u))

// in this formula (-k*u + U -u) can be rewritten as U-(k+1)*u
// if U < (k+1)*u then we must do (k+1)*u - U and subtract that from the first part of the sum in the nominator
// else we use the initial formula

{
SD59x18 sumA = p.u.add(x);
UD60x18 sumA = p.u.add(x);
sumA = sumA.mul(K).sub(p.U).add(p.u);
sumA = sumA.mul(p.U.add(x).div(L).pow(K));

SD59x18 sumB = p.U.sub(p.u).sub(K.mul(p.u)).mul(p.U.div(L).pow(K));

nom = sumA.add(sumB).mul(p.S.add(p.U));
UD60x18 negator = K.add(UNIT_60x18).mul(p.u);
if (p.U < negator) {
UD60x18 sumB = negator.sub(p.U).mul(p.U.div(L).pow(K));
nom = sumA.sub(sumB).mul(p.S.add(p.U));
} else {
UD60x18 sumB = p.U.sub(negator).mul(p.U.div(L).pow(K));
nom = sumA.add(sumB).mul(p.S.add(p.U));
}

denom = K.mul(UNIT.add(K)).mul(p.s.add(p.u));
denom = K.mul(UNIT_60x18.add(K)).mul(p.s.add(p.u));
}
SD59x18 baseFee = BASE_FEE.mul(x);
fee = uint256(baseFee.add(nom.div(denom)).unwrap());
UD60x18 baseFee = BASE_FEE.mul(x);
fee = baseFee.add(nom.div(denom)).unwrap();

fee = fee >= amount ? amount : fee;
unchecked {
out = amount - fee;
}
}
// (((u + x)*k - U + u)*((U + x)/L)**k + (-k*u + U - u)*(U/L)**k)*(S + U)/(k*(1 + k)*(s + u))

/**
* @notice checks if an asset is a valid tenderizer for `UNDERLYING`
Expand Down Expand Up @@ -570,13 +579,13 @@ contract TenderSwap is Initializable, UUPSUpgradeable, OwnableUpgradeable, SwapS
* @notice Since the LSTs to be exchanged are aTokens, and thus have a rebasing supply,
* we need to update the supplies upon a swap to correctly determine the spread of the asset.
*/
function _checkSupply(address tenderizer) internal view returns (SD59x18 s, SD59x18 S) {
function _checkSupply(address tenderizer) internal view returns (UD60x18 s, UD60x18 S) {
Data storage $ = _loadStorageSlot();

S = $.S;

s = sd(int256(Tenderizer(tenderizer).totalSupply()));
SD59x18 oldSupply = $.lastSupplyForAsset[tenderizer];
s = ud(Tenderizer(tenderizer).totalSupply());
UD60x18 oldSupply = $.lastSupplyForAsset[tenderizer];

if (oldSupply.lt(s)) {
S = S.add(s.sub(oldSupply));
Expand Down
5 changes: 2 additions & 3 deletions test/Swap.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { Tenderizer, TenderizerImmutableArgs } from "@tenderize/stake/tenderizer
import { TenderSwap, ConstructorConfig, _encodeTokenId, _decodeTokenId } from "@tenderize/swap/Swap.sol";
import { LPToken } from "@tenderize/swap/LPToken.sol";

import { SD59x18, ZERO, UNIT, unwrap, sd } from "@prb/math/SD59x18.sol";
import { UD60x18, ud, UNIT as UNIT_60x18 } from "@prb/math/UD60x18.sol";

import { SwapHarness } from "./Swap.harness.sol";
Expand Down Expand Up @@ -74,7 +73,7 @@ contract TenderSwapTest is Test {
address(tToken1), abi.encodeWithSelector(TenderizerImmutableArgs.asset.selector), abi.encode(address(underlying))
);

ConstructorConfig memory cfg = ConstructorConfig({ UNDERLYING: underlying, BASE_FEE: sd(0.0005e18), K: sd(3e18) });
ConstructorConfig memory cfg = ConstructorConfig({ UNDERLYING: underlying, BASE_FEE: ud(0.0005e18), K: ud(3e18) });
swap = new SwapHarness(cfg);
address proxy = address(new ERC1967Proxy(address(swap), ""));
swap = SwapHarness(proxy);
Expand Down Expand Up @@ -279,7 +278,7 @@ contract TenderSwapTest is Test {
(uint256 out, uint256 fee) = swap.swap(address(tToken0), amount, 0);
console.log("out %s", out);
console.log("fee %s", fee);
// just assert the call doesnt fail for now
assertTrue(fee <= out);
}

function testFuzz_swap_multiple(uint256 liquidity) public {
Expand Down

0 comments on commit a7360d0

Please sign in to comment.