Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC4626 #899

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions packages/core-contracts/contracts/interfaces/IERC4626.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC4626 {
function asset() external view returns (address assetTokenAddress);

function totalAssets() external view returns (uint256 totalManagedAssets);

function convertToShares(uint256 assets) external view returns (uint256 shares);

function convertToAssets(uint256 shares) external view returns (uint256 assets);

function maxDeposit(address receiver) external view returns (uint256 maxAssets);

function previewDeposit(uint256 assets) external view returns (uint256 shares);

function deposit(uint256 assets, address receiver) external returns (uint256 shares);

function maxMint(address receiver) external view returns (uint256 maxShares);

function previewMint(uint256 shares) external view returns (uint256 assets);

function mint(uint256 shares, address receiver) external returns (uint256 assets);

function maxWithdraw(address owner) external view returns (uint256 maxAssets);

function previewWithdraw(uint256 assets) external view returns (uint256 shares);

function withdraw(
uint256 assets,
address receiver,
address owner
) external returns (uint256 shares);

function maxRedeem(address owner) external view returns (uint256 maxShares);

function previewRedeem(uint256 shares) external view returns (uint256 assets);

function redeem(
uint256 shares,
address receiver,
address owner
) external returns (uint256 assets);
}
4 changes: 4 additions & 0 deletions packages/core-contracts/contracts/mocks/token/ERC20Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ contract ERC20Mock is ERC20 {
_mint(msg.sender, amount);
}

function mintTo(address dest, uint256 amount) external {
_mint(dest, amount);
}

function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
Expand Down
14 changes: 14 additions & 0 deletions packages/core-contracts/contracts/mocks/token/ERC4626Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../../token/ERC4626.sol";

contract ERC4626Mock is ERC4626 {
function initialize(
address assetAddress,
string memory tokenName,
string memory tokenSymbol
) public {
_initialize(assetAddress, tokenName, tokenSymbol);
}
}
16 changes: 16 additions & 0 deletions packages/core-contracts/contracts/mocks/utils/MathUtilMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,20 @@ contract MathUtilMock {
function sqrt(uint x) public pure returns (uint z) {
return MathUtil.sqrt(x);
}

function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) public pure returns (uint256) {
return MathUtil.mulDivDown(x, y, denominator);
}

function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) public pure returns (uint256) {
return MathUtil.mulDivUp(x, y, denominator);
}
}
2 changes: 1 addition & 1 deletion packages/core-contracts/contracts/token/ERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ contract ERC20 is IERC20, ERC20Storage {
return _erc20Store().decimals;
}

function totalSupply() external view override returns (uint) {
function totalSupply() public view override returns (uint) {
return _erc20Store().totalSupply;
}

Expand Down
178 changes: 178 additions & 0 deletions packages/core-contracts/contracts/token/ERC4626.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../interfaces/IERC4626.sol";
import "../interfaces/IERC20.sol";
import "../utils/MathUtil.sol";
import "./ERC20.sol";
import "./ERC4626Storage.sol";

/*
Reference implementations:
* Rari - https://github.com/Rari-Capital/solmate/blob/main/src/mixins/ERC4626.sol
* Vyper - https://github.com/fubuloubu/ERC4626
*/

contract ERC4626 is IERC4626, ERC20, ERC4626Storage {
using MathUtil for uint256;

event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);

error ZeroShares(uint256 assets, address sender, address receiver);
error ZeroAssets(uint256 shares, address sender, address receiver, address owner);

function _initialize(
address assetAddress,
string memory tokenName,
string memory tokenSymbol
) internal virtual {
ERC20._initialize(tokenName, tokenSymbol, IERC20(assetAddress).decimals());

_erc4626Store().assetAddress = assetAddress;
}

function asset() external view override returns (address) {
return _erc4626Store().assetAddress;
}

function deposit(uint256 assets, address receiver) external override returns (uint256) {
uint256 shares = previewDeposit(assets);

// Check for rounding error since we round down in previewDeposit.
if (shares == 0) {
revert ZeroShares(assets, msg.sender, receiver);
}

IERC20(_erc4626Store().assetAddress).transferFrom(msg.sender, address(this), assets);

_mint(receiver, shares);

emit Deposit(msg.sender, receiver, assets, shares);

_afterDeposit(assets, shares);

return shares;
}

function mint(uint256 shares, address receiver) external override returns (uint256) {
uint256 assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.

IERC20(_erc4626Store().assetAddress).transferFrom(msg.sender, address(this), assets);

_mint(receiver, shares);

emit Deposit(msg.sender, receiver, assets, shares);

_afterDeposit(assets, shares);

return assets;
}

function withdraw(
uint256 assets,
address receiver,
address owner
) external override returns (uint256) {
uint256 shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.

_beforeWithdraw(assets, shares);

_burn(owner, shares);

emit Withdraw(msg.sender, receiver, owner, assets, shares);

IERC20(_erc4626Store().assetAddress).transfer(receiver, assets);

return shares;
}

function redeem(
uint256 shares,
address receiver,
address owner
) external override returns (uint256) {
uint256 assets = previewRedeem(shares);

// Check for rounding error since we round down in previewRedeem.
if (assets == 0) {
revert ZeroAssets(shares, msg.sender, receiver, owner);
}

_beforeWithdraw(assets, shares);

_burn(owner, shares);

emit Withdraw(msg.sender, receiver, owner, assets, shares);

IERC20(_erc4626Store().assetAddress).transfer(receiver, assets);

return assets;
}

/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/

function totalAssets() public view override returns (uint256) {
return IERC20(_erc4626Store().assetAddress).balanceOf(address(this));
}

function convertToShares(uint256 assets) public view override returns (uint256) {
uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.

return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}

function convertToAssets(uint256 shares) public view override returns (uint256) {
uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.

return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}

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

function previewMint(uint256 shares) public view override returns (uint256) {
uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.

return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}

function previewWithdraw(uint256 assets) public view override returns (uint256) {
uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.

return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}

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

function maxDeposit(address receiver) external view virtual override returns (uint256) {
return type(uint256).max;
}

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

function maxWithdraw(address owner) external pure override returns (uint256) {
return type(uint256).max;
}

function maxRedeem(address owner) external pure override returns (uint256) {
return type(uint256).max;
}

/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/

// solhint-disable-next-line no-empty-blocks
function _beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}

// solhint-disable-next-line no-empty-blocks
function _afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
15 changes: 15 additions & 0 deletions packages/core-contracts/contracts/token/ERC4626Storage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC4626Storage {
struct ERC4626Store {
address assetAddress;
}

function _erc4626Store() internal pure returns (ERC4626Store storage store) {
assembly {
// bytes32(uint(keccak256("io.synthetix.ERC4626")) - 1)
store.slot := 0xda0ce98a951a7a931189f5e622ebcc5a4f5481440238f45f57c834a6be9f250b
}
}
}
44 changes: 44 additions & 0 deletions packages/core-contracts/contracts/utils/MathUtil.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,50 @@ pragma solidity ^0.8.0;
*/

library MathUtil {
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/

function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)

// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}

// Divide z by the denominator.
z := div(z, denominator)
}
}

function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)

// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}

// First, divide z - 1 by the denominator and add 1.
// We allow z - 1 to underflow if z is 0, because we multiply the
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
}
}

function sqrt(uint256 x) internal pure returns (uint256 z) {
assembly {
// Start off with z at 1.
Expand Down
Loading