sUSDS Base vs Mainnet

Created Diff never expires
40 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
264 lines
270 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
483 lines
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-License-Identifier: AGPL-3.0-or-later


/// SUsds.sol -- SUsds token
/// SUsds.sol


// Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico
// Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico
// Copyright (C) 2024 Dai Foundation
// Copyright (C) 2021 Dai Foundation
//
//
// This program is free software: you can redistribute it and/or modify
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// (at your option) any later version.
//
//
// This program is distributed in the hope that it will be useful,
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// GNU Affero General Public License for more details.
//
//
// You should have received a copy of the GNU Affero General Public License
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// along with this program. If not, see <https://www.gnu.org/licenses/>.


pragma solidity ^0.8.21;
pragma solidity ^0.8.21;


import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";


interface IERC1271 {
interface IERC1271 {
function isValidSignature(
function isValidSignature(
bytes32,
bytes32,
bytes memory
bytes memory
) external view returns (bytes4);
) external view returns (bytes4);
}
}


interface VatLike {
function hope(address) external;
function suck(address, address, uint256) external;
}

interface UsdsJoinLike {
function vat() external view returns (address);
function usds() external view returns (address);
function exit(address, uint256) external;
}

interface UsdsLike {
function transfer(address, uint256) external;
function transferFrom(address, address, uint256) external;
}

contract SUsds is UUPSUpgradeable {
contract SUsds is UUPSUpgradeable {

// --- Storage Variables ---

// Admin
mapping (address => uint256) public wards;
mapping (address => uint256) public wards;
// ERC20
uint256 public totalSupply;
Text moved from lines 42-44
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
mapping (address => uint256) public nonces;
// Savings yield
uint192 public chi; // The Rate Accumulator [ray]
uint64 public rho; // Time of last drip [unix epoch time]
uint256 public ssr; // The USDS Savings Rate [ray]


// --- ERC20 Data ---
// --- Constants ---

// ERC20
string public constant name = "Savings USDS";
string public constant name = "Savings USDS";
string public constant symbol = "sUSDS";
string public constant symbol = "sUSDS";
string public constant version = "1";
string public constant version = "1";
uint8 public constant decimals = 18;
uint8 public constant decimals = 18;
uint256 public totalSupply;
// Math
uint256 private constant RAY = 10 ** 27;


Text moved to lines 56-58
mapping (address => uint256) public balanceOf;
// --- Immutables ---
mapping (address => mapping (address => uint256)) public allowance;

mapping (address => uint256) public nonces;
// EIP712
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
// Savings yield
UsdsJoinLike public immutable usdsJoin;
VatLike public immutable vat;
UsdsLike public immutable usds;
address public immutable vow;


// --- Events ---
// --- Events ---

// Admin
event Rely(address indexed usr);
event Rely(address indexed usr);
event Deny(address indexed usr);
event Deny(address indexed usr);
event File(bytes32 indexed what, uint256 data);
// ERC20
event Approval(address indexed owner, address indexed spender, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
// ERC4626
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
// Referral
event Referral(uint16 indexed referral, address indexed owner, uint256 assets, uint256 shares);
// Savings yield
event Drip(uint256 chi, uint256 diff);


// --- EIP712 niceties ---
// --- Modifiers ---
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");


modifier auth {
modifier auth {
require(wards[msg.sender] == 1, "SUsds/not-authorized");
require(wards[msg.sender] == 1, "SUsds/not-authorized");
_;
_;
}
}


constructor() {
// --- Constructor ---

constructor(address usdsJoin_, address vow_) {
_disableInitializers(); // Avoid initializing in the context of the implementation
_disableInitializers(); // Avoid initializing in the context of the implementation

usdsJoin = UsdsJoinLike(usdsJoin_);
vat = VatLike(UsdsJoinLike(usdsJoin_).vat());
usds = UsdsLike(UsdsJoinLike(usdsJoin_).usds());
vow = vow_;
}
}


// --- Upgradability ---
// --- Upgradability ---


function initialize() initializer external {
function initialize() initializer external {
__UUPSUpgradeable_init();
__UUPSUpgradeable_init();


chi = uint192(RAY);
rho = uint64(block.timestamp);
ssr = RAY;
vat.hope(address(usdsJoin));
wards[msg.sender] = 1;
wards[msg.sender] = 1;
emit Rely(msg.sender);
emit Rely(msg.sender);
}
}


function _authorizeUpgrade(address newImplementation) internal override auth {}
function _authorizeUpgrade(address newImplementation) internal override auth {}


function getImplementation() external view returns (address) {
function getImplementation() external view returns (address) {
return ERC1967Utils.getImplementation();
return ERC1967Utils.getImplementation();
}
}


// --- Internals ---

// EIP712

function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
return keccak256(
return keccak256(
abi.encode(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256(bytes(name)),
keccak256(bytes(version)),
keccak256(bytes(version)),
chainId,
chainId,
address(this)
address(this)
)
)
);
);
}
}


function DOMAIN_SEPARATOR() external view returns (bytes32) {
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return _calculateDomainSeparator(block.chainid);
return _calculateDomainSeparator(block.chainid);
}
}


// --- Administration ---
// Math

function _rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {
assembly {
switch x case 0 {switch n case 0 {z := RAY} default {z := 0}}
default {
switch mod(n, 2) case 0 { z := RAY } default { z := x }
let half := div(RAY, 2) // for rounding.
for { n := div(n, 2) } n { n := div(n,2) } {
let xx := mul(x, x)
if iszero(eq(div(xx, x), x)) { revert(0,0) }
let xxRound := add(xx, half)
if lt(xxRound, xx) { revert(0,0) }
x := div(xxRound, RAY)
if mod(n,2) {
let zx := mul(z, x)
if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) }
let zxRound := add(zx, half)
if lt(zxRound, zx) { revert(0,0) }
z := div(zxRound, RAY)
}
}
}
}
}

function _divup(uint256 x, uint256 y) internal pure returns (uint256 z) {
// Note: _divup(0,0) will return 0 differing from natural solidity division
unchecked {
z = x != 0 ? ((x - 1) / y) + 1 : 0;
}
}

// --- Admin external functions ---

function rely(address usr) external auth {
function rely(address usr) external auth {
wards[usr] = 1;
wards[usr] = 1;
emit Rely(usr);
emit Rely(usr);
}
}


function deny(address usr) external auth {
function deny(address usr) external auth {
wards[usr] = 0;
wards[usr] = 0;
emit Deny(usr);
emit Deny(usr);
}
}


function file(bytes32 what, uint256 data) external auth {
if (what == "ssr") {
require(data >= RAY, "SUsds/wrong-ssr-value");
require(rho == block.timestamp, "SUsds/chi-not-up-to-date");
ssr = data;
} else revert("SUsds/file-unrecognized-param");
emit File(what, data);
}

// --- Savings Rate Accumulation external/internal function ---

function drip() public returns (uint256 nChi) {
(uint256 chi_, uint256 rho_) = (chi, rho);
uint256 diff;
if (block.timestamp > rho_) {
nChi = _rpow(ssr, block.timestamp - rho_) * chi_ / RAY;
uint256 totalSupply_ = totalSupply;
diff = totalSupply_ * nChi / RAY - totalSupply_ * chi_ / RAY;
vat.suck(address(vow), address(this), diff * RAY);
usdsJoin.exit(address(this), diff);
chi = uint192(nChi); // safe as nChi is limited to maxUint256/RAY (which is < maxUint192)
rho = uint64(block.timestamp);
} else {
nChi = chi_;
}
emit Drip(nChi, diff);
}

// --- ERC20 Mutations ---
// --- ERC20 Mutations ---

function transfer(address to, uint256 value) external returns (bool) {
function transfer(address to, uint256 value) external returns (bool) {
require(to != address(0) && to != address(this), "SUsds/invalid-address");
require(to != address(0) && to != address(this), "SUsds/invalid-address");
uint256 balance = balanceOf[msg.sender];
uint256 balance = balanceOf[msg.sender];
require(balance >= value, "SUsds/insufficient-balance");
require(balance >= value, "SUsds/insufficient-balance");


unchecked {
unchecked {
balanceOf[msg.sender] = balance - value;
balanceOf[msg.sender] = balance - value;
balanceOf[to] += value; // note: we don't need an overflow check here b/c sum of all balances == totalSupply
balanceOf[to] += value; // note: we don't need an overflow check here b/c sum of all balances == totalSupply
}
}


emit Transfer(msg.sender, to, value);
emit Transfer(msg.sender, to, value);


return true;
return true;
}
}


function transferFrom(address from, address to, uint256 value) external returns (bool) {
function transferFrom(address from, address to, uint256 value) external returns (bool) {
require(to != address(0) && to != address(this), "SUsds/invalid-address");
require(to != address(0) && to != address(this), "SUsds/invalid-address");
uint256 balance = balanceOf[from];
uint256 balance = balanceOf[from];
require(balance >= value, "SUsds/insufficient-balance");
require(balance >= value, "SUsds/insufficient-balance");


if (from != msg.sender) {
if (from != msg.sender) {
uint256 allowed = allowance[from][msg.sender];
uint256 allowed = allowance[from][msg.sender];
if (allowed != type(uint256).max) {
if (allowed != type(uint256).max) {
require(allowed >= value, "SUsds/insufficient-allowance");
require(allowed >= value, "SUsds/insufficient-allowance");


unchecked {
unchecked {
allowance[from][msg.sender] = allowed - value;
allowance[from][msg.sender] = allowed - value;
}
}
}
}
}
}


unchecked {
unchecked {
balanceOf[from] = balance - value;
balanceOf[from] = balance - value;
balanceOf[to] += value; // note: we don't need an overflow check here b/c sum of all balances == totalSupply
balanceOf[to] += value; // note: we don't need an overflow check here b/c sum of all balances == totalSupply
}
}


emit Transfer(from, to, value);
emit Transfer(from, to, value);


return true;
return true;
}
}


function approve(address spender, uint256 value) external returns (bool) {
function approve(address spender, uint256 value) external returns (bool) {
allowance[msg.sender][spender] = value;
allowance[msg.sender][spender] = value;


emit Approval(msg.sender, spender, value);
emit Approval(msg.sender, spender, value);


return true;
return true;
}
}


// --- Mint/Burn ---
// --- Mint/Burn Internal ---
function mint(address to, uint256 value) external auth {

require(to != address(0) && to != address(this), "SUsds/invalid-address");
function _mint(uint256 assets, uint256 shares, address receiver) internal {
require(receiver != address(0) && receiver != address(this), "SUsds/invalid-address");

usds.transferFrom(msg.sender, address(this), assets);

unchecked {
unchecked {
balanceOf[to] = balanceOf[to] + value; // note: we don't need an overflow check here b/c balanceOf[to] <= totalSupply and there is an overflow check below
balanceOf[receiver] = balanceOf[receiver] + shares; // note: we don't need an overflow check here b/c balanceOf[receiver] <= totalSupply
totalSupply = totalSupply + shares; // note: we don't need an overflow check here b/c shares totalSupply will always be <= usds totalSupply
}
}
totalSupply = totalSupply + value;


emit Transfer(address(0), to, value);
emit Deposit(msg.sender, receiver, assets, shares);
emit Transfer(address(0), receiver, shares);
}
}


function burn(address from, uint256 value) external {
function _burn(uint256 assets, uint256 shares, address receiver, address owner) internal {
uint256 balance = balanceOf[from];
uint256 balance = balanceOf[owner];
require(balance >= value, "SUsds/insufficient-balance");
require(balance >= shares, "SUsds/insufficient-balance");


if (from != msg.sender) {
if (owner != msg.sender) {
uint256 allowed = allowance[from][msg.sender];
uint256 allowed = allowance[owner][msg.sender];
if (allowed != type(uint256).max) {
if (allowed != type(uint256).max) {
require(allowed >= value, "SUsds/insufficient-allowance");
require(allowed >= shares, "SUsds/insufficient-allowance");


unchecked {
unchecked {
allowance[from][msg.sender] = allowed - value;
allowance[owner][msg.sender] = allowed - shares;
}
}
}
}
}
}


unchecked {
unchecked {
balanceOf[from] = balance - value; // note: we don't need overflow checks b/c require(balance >= value) and balance <= totalSupply
balanceOf[owner] = balance - shares; // note: we don't need overflow checks b/c require(balance >= shares) and balance <= totalSupply
totalSupply = totalSupply - value;
totalSupply = totalSupply - shares;
}
}


emit Transfer(from, address(0), value);
usds.transfer(receiver, assets);

emit Transfer(owner, address(0), shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
}

// --- ERC-4626 ---

function asset() external view returns (address) {
return address(usds);
}

function totalAssets() external view returns (uint256) {
return convertToAssets(totalSupply);
}

function convertToShares(uint256 assets) public view returns (uint256) {
uint256 chi_ = (block.timestamp > rho) ? _rpow(ssr, block.timestamp - rho) * chi / RAY : chi;
return assets * RAY / chi_;
}

function convertToAssets(uint256 shares) public view returns (uint256) {
uint256 chi_ = (block.timestamp > rho) ? _rpow(ssr, block.timestamp - rho) * chi / RAY : chi;
return shares * chi_ / RAY;
}

function maxDeposit(address) external pure returns (uint256) {
return type(uint256).max;
}

function previewDeposit(uint256 assets) external view returns (uint256) {
return convertToShares(assets);
}

function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
shares = assets * RAY / drip();
_mint(assets, shares, receiver);
}

function deposit(uint256 assets, address receiver, uint16 referral) external returns (uint256 shares) {
shares = deposit(assets, receiver);
emit Referral(referral, receiver, assets, shares);
}

function maxMint(address) external pure returns (uint256) {
return type(uint256).max;
}

function previewMint(uint256 shares) external view returns (uint256) {
uint256 chi_ = (block.timestamp > rho) ? _rpow(ssr, block.timestamp - rho) * chi / RAY : chi;
return _divup(shares * chi_, RAY);
}

function mint(uint256 shares, address receiver) public returns (uint256 assets) {
assets = _divup(shares * drip(), RAY);
_mint(assets, shares, receiver);
}

function mint(uint256 shares, address receiver, uint16 referral) external returns (uint256 assets) {
assets = mint(shares, receiver);
emit Referral(referral, receiver, assets, shares);
}

function maxWithdraw(address owner) external view returns (uint256) {
return convertToAssets(balanceOf[owner]);
}

function previewWithdraw(uint256 assets) external view returns (uint256) {
uint256 chi_ = (block.timestamp > rho) ? _rpow(ssr, block.timestamp - rho) * chi / RAY : chi;
return _divup(assets * RAY, chi_);
}

function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) {
shares = _divup(assets * RAY, drip());
_burn(assets, shares, receiver, owner);
}

function maxRedeem(address owner) external view returns (uint256) {
return balanceOf[owner];
}

function previewRedeem(uint256 shares) external view returns (uint256) {
return convertToAssets(shares);
}

function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {
assets = shares * drip() / RAY;
_burn(assets, shares, receiver, owner);
}
}


// --- Approve by signature ---
// --- Approve by signature ---

function _isValidSignature(
function _isValidSignature(
address signer,
address signer,
bytes32 digest,
bytes32 digest,
bytes memory signature
bytes memory signature
) internal view returns (bool valid) {
) internal view returns (bool valid) {
if (signature.length == 65) {
if (signature.length == 65) {
bytes32 r;
bytes32 r;
bytes32 s;
bytes32 s;
uint8 v;
uint8 v;
assembly {
assembly {
r := mload(add(signature, 0x20))
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
v := byte(0, mload(add(signature, 0x60)))
}
}
if (signer == ecrecover(digest, v, r, s)) {
if (signer == ecrecover(digest, v, r, s)) {
return true;
return true;
}
}
}
}


if (signer.code.length > 0) {
if (signer.code.length > 0) {
(bool success, bytes memory result) = signer.staticcall(
(bool success, bytes memory result) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (digest, signature))
abi.encodeCall(IERC1271.isValidSignature, (digest, signature))
);
);
valid = (success &&
valid = (success &&
result.length == 32 &&
result.length == 32 &&
abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
}
}
}
}


function permit(
function permit(
address owner,
address owner,
address spender,
address spender,
uint256 value,
uint256 value,
uint256 deadline,
uint256 deadline,
bytes memory signature
bytes memory signature
) public {
) public {
require(block.timestamp <= deadline, "SUsds/permit-expired");
require(block.timestamp <= deadline, "SUsds/permit-expired");
require(owner != address(0), "SUsds/invalid-owner");
require(owner != address(0), "SUsds/invalid-owner");


uint256 nonce;
uint256 nonce;
unchecked { nonce = nonces[owner]++; }
unchecked { nonce = nonces[owner]++; }


bytes32 digest =
bytes32 digest =
keccak256(abi.encodePacked(
keccak256(abi.encodePacked(
"\x19\x01",
"\x19\x01",
_calculateDomainSeparator(block.chainid),
_calculateDomainSeparator(block.chainid),
keccak256(abi.encode(
keccak256(abi.encode(
PERMIT_TYPEHASH,
PERMIT_TYPEHASH,
owner,
owner,
spender,
spender,
value,
value,
nonce,
nonce,
deadline
deadline
))
))
));
));


require(_isValidSignature(owner, digest, signature), "SUsds/invalid-permit");
require(_isValidSignature(owner, digest, signature), "SUsds/invalid-permit");


allowance[owner][spender] = value;
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
emit Approval(owner, spender, value);
}
}


function permit(
function permit(
address owner,
address owner,
address spender,
address spender,
uint256 value,
uint256 value,
uint256 deadline,
uint256 deadline,
uint8 v,
uint8 v,
bytes32 r,
bytes32 r,
bytes32 s
bytes32 s
) external {
) external {
permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
}
}
}
}