From 720e5e0a0bceefb47ab20e0076689e81812d42a3 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 12 Sep 2023 09:40:24 +0200 Subject: [PATCH 001/125] Exemplary ERC4626 specs --- lesson4_reading/erc4626/src/ERC4626.sol | 180 ++++++++++++++ .../erc4626/src/ERC4626InflationAttack.sol | 189 ++++++++++++++ .../certoraRun-FunctionalAccountingProps.conf | 14 ++ .../conf/certoraRun-InflationAttack.conf | 14 ++ .../certoraRun-MonotonicityInvariant.conf | 14 ++ .../conf/certoraRun-MustNotRevertProps.conf | 14 ++ .../certoraRun-RedeemUsingApprovalProps.conf | 14 ++ .../conf/certoraRun-RoundingProps.conf | 14 ++ .../conf/certoraRun-SecurityProps.conf | 14 ++ .../ERC4626-FunctionalAccountingProps.spec | 130 ++++++++++ .../specs/ERC4626-InflationAttack.spec | 162 ++++++++++++ .../specs/ERC4626-MonotonicityInvariant.spec | 105 ++++++++ .../specs/ERC4626-MustNotRevertProps.spec | 89 +++++++ .../ERC4626-RedeemUsingApprovalProps.spec | 120 +++++++++ .../certora/specs/ERC4626-RoundingProps.spec | 83 +++++++ .../certora/specs/ERC4626-SecurityProps.spec | 77 ++++++ .../erc4626/src/certora/specs/Properties.md | 129 ++++++++++ .../erc4626/src/libs/FixedPointMathLib.sol | 230 ++++++++++++++++++ lesson4_reading/erc4626/src/tokens/ERC20.sol | 116 +++++++++ 19 files changed, 1708 insertions(+) create mode 100644 lesson4_reading/erc4626/src/ERC4626.sol create mode 100644 lesson4_reading/erc4626/src/ERC4626InflationAttack.sol create mode 100644 lesson4_reading/erc4626/src/certora/conf/certoraRun-FunctionalAccountingProps.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf/certoraRun-InflationAttack.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf/certoraRun-MonotonicityInvariant.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf/certoraRun-MustNotRevertProps.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf/certoraRun-RedeemUsingApprovalProps.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf/certoraRun-RoundingProps.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf/certoraRun-SecurityProps.conf create mode 100644 lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs/Properties.md create mode 100644 lesson4_reading/erc4626/src/libs/FixedPointMathLib.sol create mode 100644 lesson4_reading/erc4626/src/tokens/ERC20.sol diff --git a/lesson4_reading/erc4626/src/ERC4626.sol b/lesson4_reading/erc4626/src/ERC4626.sol new file mode 100644 index 0000000..cd62a31 --- /dev/null +++ b/lesson4_reading/erc4626/src/ERC4626.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "./tokens/ERC20.sol"; +import {SafeTransferLib} from "./libs/SafeTransferLib.sol"; +import {FixedPointMathLib} from "./libs/FixedPointMathLib.sol"; + +/// @notice Minimal ERC4626 tokenized Vault implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol) +/// @dev This contract implements the `totalAssets()` function by simply +/// returning the contract's balance. +/// @notice This contract is open to an attack where the first depositor +/// can steal all the other users' funds. +/// This can be done by front-running the deposit transaction and +/// transferring enough tokens to zero the shares calculation. +contract ERC4626 is ERC20 { + using SafeTransferLib for ERC20; + using FixedPointMathLib for uint256; + + // EVENTS + + 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 + ); + + // IMMUTABLES + + ERC20 public immutable asset; + + constructor( + ERC20 _asset, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol, _asset.decimals()) { + asset = _asset; + } + + // DEPOSIT/WITHDRAWAL LOGIC + + function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { + // Check for rounding error since we round down in previewDeposit. + require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); + + // Need to transfer before minting or ERC777s could reenter. + asset.safeTransferFrom(msg.sender, address(this), assets); + + _mint(receiver, shares); + + emit Deposit(msg.sender, receiver, assets, shares); + + afterDeposit(assets, shares); + } + + function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) { + assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up. + + // Need to transfer before minting or ERC777s could reenter. + asset.safeTransferFrom(msg.sender, address(this), assets); + + _mint(receiver, shares); + + emit Deposit(msg.sender, receiver, assets, shares); + + afterDeposit(assets, shares); + } + + function withdraw( + uint256 assets, + address receiver, + address owner + ) public virtual returns (uint256 shares) { + shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up. + + if (msg.sender != owner) { + uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; + } + + beforeWithdraw(assets, shares); + + _burn(owner, shares); + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + asset.safeTransfer(receiver, assets); + } + + function redeem( + uint256 shares, + address receiver, + address owner + ) public virtual returns (uint256 assets) { + if (msg.sender != owner) { + uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; + } + + // Check for rounding error since we round down in previewRedeem. + require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS"); + + beforeWithdraw(assets, shares); + + _burn(owner, shares); + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + asset.safeTransfer(receiver, assets); + } + + // ACCOUNTING LOGIC + + function totalAssets() public view returns (uint256) { + return asset.balanceOf(address(this)); + } + + function convertToShares(uint256 assets) public view virtual 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 virtual 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 virtual returns (uint256) { + return convertToShares(assets); + } + + function previewMint(uint256 shares) public view virtual 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 virtual 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 virtual returns (uint256) { + return convertToAssets(shares); + } + + // DEPOSIT/WITHDRAWAL LIMIT LOGIC + + function maxDeposit(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + function maxWithdraw(address owner) public view virtual returns (uint256) { + return convertToAssets(balanceOf[owner]); + } + + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf[owner]; + } + + // INTERNAL HOOKS LOGIC + + function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {} + + function afterDeposit(uint256 assets, uint256 shares) internal virtual {} + +} diff --git a/lesson4_reading/erc4626/src/ERC4626InflationAttack.sol b/lesson4_reading/erc4626/src/ERC4626InflationAttack.sol new file mode 100644 index 0000000..226e2e8 --- /dev/null +++ b/lesson4_reading/erc4626/src/ERC4626InflationAttack.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "./tokens/ERC20.sol"; +import {SafeTransferLib} from "./libs/SafeTransferLib.sol"; +import {FixedPointMathLib} from "./libs/FixedPointMathLib.sol"; + +/// @notice Minimal ERC4626 tokenized Vault implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol) +/// @dev This contract implements the `totalAssets()` function by simply +/// returning the contract's balance. +/// @notice This contract is open to an attack where the first depositor +/// can steal all the other users' funds. +/// This can be done by front-running the deposit transaction and +/// transferring enough tokens to zero the shares calculation. +contract ERC4626InflationAttack is ERC20 { + using SafeTransferLib for ERC20; + using FixedPointMathLib for uint256; + + // EVENTS + + 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 + ); + + // IMMUTABLES + + ERC20 public immutable asset; + + constructor( + ERC20 _asset, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol, _asset.decimals()) { + asset = _asset; + } + + // DEPOSIT/WITHDRAWAL LOGIC + + function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { + // Check for rounding error since we round down in previewDeposit. + //Commented out to make the contract vulnerable to inflation attack + + //Added minimal change required for the InflationAttack to work. + //require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); + shares = previewDeposit(assets); + + // Need to transfer before minting or ERC777s could reenter. + asset.safeTransferFrom(msg.sender, address(this), assets); + + _mint(receiver, shares); + + emit Deposit(msg.sender, receiver, assets, shares); + + afterDeposit(assets, shares); + } + + function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) { + assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up. + + // Need to transfer before minting or ERC777s could reenter. + asset.safeTransferFrom(msg.sender, address(this), assets); + + _mint(receiver, shares); + + emit Deposit(msg.sender, receiver, assets, shares); + + afterDeposit(assets, shares); + } + + function withdraw( + uint256 assets, + address receiver, + address owner + ) public virtual returns (uint256 shares) { + shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up. + + if (msg.sender != owner) { + uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; + } + + beforeWithdraw(assets, shares); + + _burn(owner, shares); + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + asset.safeTransfer(receiver, assets); + } + + function redeem( + uint256 shares, + address receiver, + address owner + ) public virtual returns (uint256 assets) { + if (msg.sender != owner) { + uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; + } + + // Check for rounding error since we round down in previewRedeem. + require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS"); + //Don't check for rounding errors to allow inflation attack. + + beforeWithdraw(assets, shares); + + _burn(owner, shares); + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + asset.safeTransfer(receiver, assets); + } + + // ACCOUNTING LOGIC + + function totalAssets() public view returns (uint256) { + return asset.balanceOf(address(this)); + } + + function convertToShares(uint256 assets) public view virtual 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 virtual 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 virtual returns (uint256) { + return convertToShares(assets); + } + + function previewMint(uint256 shares) public view virtual 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 virtual 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 virtual returns (uint256) { + return convertToAssets(shares); + } + + // DEPOSIT/WITHDRAWAL LIMIT LOGIC + + function maxDeposit(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + function maxWithdraw(address owner) public view virtual returns (uint256) { + return convertToAssets(balanceOf[owner]); + } + + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf[owner]; + } + + // INTERNAL HOOKS LOGIC + + function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {} + + function afterDeposit(uint256 assets, uint256 shares) internal virtual {} + + //Logic added for Certora Prover. TODO remove as cleanup of specs/ERC4626-FunctionalAccountingProps.spec. + function getUserBalance(address addr) external view returns (uint256) { + return address(addr).balance; + } +} diff --git a/lesson4_reading/erc4626/src/certora/conf/certoraRun-FunctionalAccountingProps.conf b/lesson4_reading/erc4626/src/certora/conf/certoraRun-FunctionalAccountingProps.conf new file mode 100644 index 0000000..e0552bb --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf/certoraRun-FunctionalAccountingProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/ERC4626.sol", + "src/tokens/ERC20.sol", + ], + "verify": "ERC4626:src/certora/specs/ERC4626-FunctionalAccountingProps.spec", + "link": ["ERC4626:asset=ERC20"], + "solc": "solc8.0", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf/certoraRun-InflationAttack.conf b/lesson4_reading/erc4626/src/certora/conf/certoraRun-InflationAttack.conf new file mode 100644 index 0000000..e3e9ae5 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf/certoraRun-InflationAttack.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/ERC4626InflationAttack.sol", + "src/tokens/ERC20.sol", + ], + "verify": "ERC4626InflationAttack:src/certora/specs/ERC4626-InflationAttack.spec", + "link": ["ERC4626InflationAttack:asset=ERC20"], + "solc": "solc8.0", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf/certoraRun-MonotonicityInvariant.conf b/lesson4_reading/erc4626/src/certora/conf/certoraRun-MonotonicityInvariant.conf new file mode 100644 index 0000000..387d479 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf/certoraRun-MonotonicityInvariant.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/ERC4626.sol", + "src/tokens/ERC20.sol", + ], + "verify": "ERC4626:src/certora/specs/ERC4626-MonotonicityInvariant.spec", + "link": ["ERC4626:asset=ERC20"], + "solc": "solc8.0", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf/certoraRun-MustNotRevertProps.conf b/lesson4_reading/erc4626/src/certora/conf/certoraRun-MustNotRevertProps.conf new file mode 100644 index 0000000..6a7ab1e --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf/certoraRun-MustNotRevertProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/ERC4626.sol", + "src/tokens/ERC20.sol", + ], + "verify": "ERC4626:src/certora/specs/ERC4626-MustNotRevertProps.spec", + "link": ["ERC4626:asset=ERC20"], + "solc": "solc8.0", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf/certoraRun-RedeemUsingApprovalProps.conf b/lesson4_reading/erc4626/src/certora/conf/certoraRun-RedeemUsingApprovalProps.conf new file mode 100644 index 0000000..7a156cf --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf/certoraRun-RedeemUsingApprovalProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/ERC4626.sol", + "src/tokens/ERC20.sol", + ], + "verify": "ERC4626:src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec", + "link": ["ERC4626:asset=ERC20"], + "solc": "solc8.0", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf/certoraRun-RoundingProps.conf new file mode 100644 index 0000000..fb8be14 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf/certoraRun-RoundingProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/ERC4626.sol", + "src/tokens/ERC20.sol", + ], + "verify": "ERC4626:src/certora/specs/ERC4626-RoundingProps.spec", + "link": ["ERC4626:asset=ERC20"], + "solc": "solc8.0", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf/certoraRun-SecurityProps.conf b/lesson4_reading/erc4626/src/certora/conf/certoraRun-SecurityProps.conf new file mode 100644 index 0000000..08a8b57 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf/certoraRun-SecurityProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/ERC4626.sol", + "src/tokens/ERC20.sol", + ], + "verify": "ERC4626:src/certora/specs/ERC4626-SecurityProps.spec", + "link": ["ERC4626:asset=ERC20"], + "solc": "solc8.0", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec new file mode 100644 index 0000000..89bfdb0 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec @@ -0,0 +1,130 @@ +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to __ERC20 as of import that already declares _ERC20. +using ERC20 as __ERC20; + +//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupply; + +methods { + function __ERC20.balanceOf(address) external returns uint256 envfree; + + function balanceOf(address) external returns uint256 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function totalSupply() external returns uint256 envfree; +} + +rule depositProperties(uint256 assets, address receiver, address owner){ + safeAssumptions(); + env e; + //Not an assumption, but just creating an alias for e.msg.sender. Tryout if uint256 owver = e.msg.sender is a better choice here? + require(e.msg.sender == owner); + + //The caller may not be the currentContract. Is that a fair assumption? + //Not sure if solidity allows a way to to fake e.msg.sender attribute. (Delegate call, reflection,...?) + require(owner != currentContract); + + + mathint ownerAssetsBefore = __ERC20.balanceOf(owner); + mathint receiverSharesBefore = balanceOf(receiver); + + mathint previewShares = previewDeposit(assets); + mathint shares = deposit(e, assets, receiver); + + mathint ownerAssetsAfter = __ERC20.balanceOf(owner); + mathint receiverSharesAfter = balanceOf(receiver); + + assert ownerAssetsAfter + assets == ownerAssetsBefore; + assert receiverSharesAfter - shares == receiverSharesBefore; + assert shares >= previewShares; +} + +rule mintProperties(uint256 shares, address receiver, address owner){ + safeAssumptions(); + env e; + require(e.msg.sender == owner); + + //The caller may not be the currentContract. Is that a fair assumption? + //Not sure if solidity allows a way to to fake e.msg.sender attribute. (Delegate call, reflection,...?) + require(owner != currentContract); + + + mathint ownerAssetsBefore = __ERC20.balanceOf(owner); + mathint receiverSharesBefore = balanceOf(receiver); + + mathint previewAssets = previewMint(shares); + mathint assets = mint(e, shares, receiver); + + + mathint ownerAssetsAfter = __ERC20.balanceOf(owner); + mathint receiverSharesAfter = balanceOf(receiver); + + assert ownerAssetsAfter + assets == ownerAssetsBefore; + assert receiverSharesAfter - shares == receiverSharesBefore; + assert assets <= previewAssets; +} + + + +rule withdrawProperties(uint256 assets, address receiver, address owner){ + safeAssumptions(); + env e; + + //The caller may not be the currentContract. Is that a fair assumption? + //Not sure if solidity allows a way to to fake e.msg.sender attribute. (Delegate call, reflection,...?) + require(e.msg.sender != currentContract); + + mathint ownerSharesBefore = balanceOf(owner); + mathint receiverAssetsBefore = __ERC20.balanceOf(receiver); + + mathint previewShares = previewWithdraw(assets); + mathint shares = withdraw(e, assets, receiver, owner); + + + mathint ownerSharesAfter = balanceOf(owner); + mathint receiverAssetsAfter = __ERC20.balanceOf(receiver); + + assert ownerSharesAfter + shares == ownerSharesBefore; + assert receiver != currentContract => receiverAssetsAfter - assets == receiverAssetsBefore; + + //Is this according to specifications or a bug? Couldn't find a clear answer to it. Probably yes, receiverAssets remain unchanged, at least don't increase. + assert receiver == currentContract => receiverAssetsAfter == receiverAssetsBefore; + + assert shares <= previewShares; +} + + +rule redeemProperties(uint256 shares, address receiver, address owner){ + safeAssumptions(); + env e; + + //The caller may not be the currentContract. Is that a fair assumption? + //Not sure if solidity allows a way to to fake e.msg.sender attribute. (Delegate call, reflection,...?) + require(e.msg.sender != currentContract); + + mathint ownerSharesBefore = balanceOf(owner); + mathint receiverAssetsBefore = __ERC20.balanceOf(receiver); + + mathint previewAssets = previewRedeem(shares); + mathint assets = redeem(e, shares, receiver, owner); + + + mathint ownerSharesAfter = balanceOf(owner); + mathint receiverAssetsAfter = __ERC20.balanceOf(receiver); + + assert ownerSharesAfter + shares == ownerSharesBefore; + assert receiver != currentContract => receiverAssetsAfter - assets == receiverAssetsBefore; + + //Is this according to specifications or a bug? Couldn't find a clear answer to it. Probably yes, receiverAssets remain unchanged, at least don't increase. + assert receiver == currentContract => receiverAssetsAfter == receiverAssetsBefore; + + assert assets >= previewAssets; +} + +//Current results: https://prover.certora.com/output/53900/7538cffeb6f5475580378e6b3f25cefd?anonymousKey=a51328c86e7ebd55e0a9bab660fb6dc241ef0cbd \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec new file mode 100644 index 0000000..f3348e2 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec @@ -0,0 +1,162 @@ + +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. +using ERC20 as __ERC20; + +//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupply; + +methods{ + function __ERC20.allowance(address,address) external returns uint256 envfree; + function __ERC20.balanceOf(address) external returns uint256 envfree; + function __ERC20.decimals() external returns uint8 envfree; + function __ERC20.totalSupply() external returns uint256 envfree; + + function balanceOf(address) external returns uint256 envfree; + function convertToAssets(uint256) external returns uint256 envfree; + function convertToShares(uint256) external returns uint256 envfree; + function decimals() external returns uint8 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function totalSupply() external returns uint256 envfree; +} + + + +rule simpleVersionOfVulnerableAttack(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_ownver) { + env e; + safeAssumptions(); + address attacker = e.msg.sender; + + require(balanceOf(attacker) == 0); + require(balanceOf(deposit_receiver) == 0); + require(balanceOf(redeem_receiver) == 0); + require(balanceOf(redeem_ownver) == 0); + + require(attacker != currentContract); + + uint256 shares = deposit(e, assets, deposit_receiver); + + //In the inflationAttack there are 2 steps that we don't model here! + + uint256 receivedAssets = redeem(e, shares, redeem_receiver, redeem_ownver); + assert(receivedAssets <= assets); +} + + + + +//Source: Medium Article by Shao https://tienshaoku.medium.com/eip-4626-inflation-sandwich-attack-deep-dive-and-how-to-solve-it-9e3e320cc3f1 +rule vulnerableToInflationAttack(address attacker, address victim, address deposit1_receiver, address deposit2_victim_receiver,address redeem_receiver,address redeem_ownver ){ + + //Doesn't work properly...Retry later. + /*requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20;*/ + //Doesn't work + //require forall address x. balanceOf(x) <= totalSupply(); + //Doesn't work + //require forall address y. __ERC20.balanceOf(y) <= __ERC20.totalSupply(); + safeAssumptions(); + uint256 oneEther; + uint256 oneWei; + + require(oneWei > 0); + require(oneEther > 0); + + mathint assetsAttackerPreAttack = to_mathint(oneEther) + to_mathint(oneWei); + uint8 ERC4626decimals = decimals(); + uint8 ERC20decimals = __ERC20.decimals(); + + require(attacker != currentContract); + require(attacker != __ERC20); + require(attacker != 0); + require(victim != currentContract); + require(victim != __ERC20); + require(victim != 0); + require(victim != attacker); + + //Following the pattern "First Deposit" of the article. + require(totalSupply() == 0); + require(totalAssets() == 0); + + //Duplicated all requireInvariants + //Doesn't work either.... + require(balanceOf(attacker) == 0); + require(balanceOf(victim) == 0); + require(balanceOf(deposit1_receiver) == 0); + require(balanceOf(deposit2_victim_receiver) == 0); + require(balanceOf(redeem_receiver) == 0); + require(balanceOf(redeem_ownver) == 0); + + uint256 before_step_1_totalSupply = totalSupply(); + uint256 before_step_1_totalAssets = totalAssets(); + + /** + * Step 1: the attacker front-runs the depositor and deposits 1 wei WETH and gets 1 share: since totalSupply is 0, shares = 1 * 10**18 / 10**18 = 1 + */ + env e1; + require(e1.msg.sender == attacker); + uint256 firstShares = deposit(e1, oneEther, deposit1_receiver); + + uint256 before_step_2_totalSupply = totalSupply(); + uint256 before_step_2_totalAssets = totalAssets(); + + env e2; + require(e2.msg.sender == attacker); + require(e2.block.timestamp > e1.block.timestamp); + + require(__ERC20.balanceOf(attacker) >= oneWei); + + /** + * Step 2: the attacker also transfers 1 * 1e18 weiWETH, making the totalAssets() WETH balance of the vault become 1e18 + 1 wei + */ + __ERC20.transferFrom(e2, attacker, currentContract, oneWei); + require(__ERC20.balanceOf(currentContract) > 0); + + uint256 before_step_3_totalSupply = totalSupply(); + uint256 before_step_3_totalAssets = totalAssets(); + + //assert before_step_3_totalSupply > 0; + + + /** + * Step 3: + * The spied-on depositor deposits 1e18 wei WETH. However, the depositor gets 0 shares: 1e18 * 1 (totalSupply) / (1e18 + 1) = 1e18 / (1e18 + 1) = 0. + * Since the depositor gets 0 shares, totalSupply() remains at 1 + */ + env e3; + require(e3.msg.sender == victim); + require(e3.block.timestamp > e2.block.timestamp); + uint256 previweAssets = previewDeposit(oneWei); + uint256 victimShares = deposit(e3, oneWei, deposit2_victim_receiver); + + /** + * Step 4: the attacker still has the 1 only share ever minted and thus the withdrawal of + * that 1 share takes away everything in the vault, including the depositor’s 1e18 weiWETH + */ + + uint256 before_step_4_totalSupply = totalSupply(); + uint256 before_step_4_totalAssets = totalAssets(); + uint256 random; + env e4; + require(e4.msg.sender == attacker); + require(e4.block.timestamp > e3.block.timestamp); + //TODO: can attacker actually withdraw `convertToAssets(before_step_4_totalSupply)` or only `assetsAttackerPreAttack` + mathint assetsAttackerPostAttack = redeem(e4, before_step_4_totalSupply, redeem_receiver, redeem_ownver); + + uint256 finalTotalAssets = totalAssets(); + uint256 finalTotalSupply = totalSupply(); + mathint assetsAttackerGained = assetsAttackerPostAttack - assetsAttackerPreAttack; + + assert assetsAttackerPreAttack >= assetsAttackerPostAttack, "The attacker gained assets."; +} + +//Current results: https://prover.certora.com/output/53900/478fe72720584bc6af003f4f9bf6e4c9?anonymousKey=56d469faee96a177b669d70fe199a2e318e2c714 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec new file mode 100644 index 0000000..64f8309 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec @@ -0,0 +1,105 @@ + +using ERC20 as _ERC20; + +methods { + function totalSupply() external returns uint256 envfree; + function balanceOf(address) external returns uint256 envfree; + function allowance(address, address) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + function _ERC20.totalSupply() external returns uint256 envfree; +} + +function safeAssumptions(){ + requireInvariant totalAssetsZeroImpliesTotalSupplyZero; + requireInvariant sumOfBalancesEqualsTotalSupply; + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; +} + +rule assetAndShareMonotonicy(){ + + safeAssumptions(); + uint256 totalAssetsBefore = totalAssets(); + uint256 totalSupplyBefore = totalSupply(); + + method f; + env e; + uint256 amount; + address receiver; + address owner; + if(f.selector == sig:mint(uint,address).selector){ + mint(e, amount, receiver); + } else if(f.selector == sig:withdraw(uint,address,address).selector){ + withdraw(e, amount, receiver, owner); + } else if(f.selector == sig:deposit(uint,address).selector){ + deposit(e, amount, receiver); + } else if(f.selector == sig:redeem(uint,address,address).selector){ + redeem(e, amount, receiver, owner); + } else { + calldataarg args; + f(e,args); + } + + uint256 totalAssetsAfter = totalAssets(); + uint256 totalSupplyAfter = totalSupply(); + + require(e.msg.sender != currentContract); + assert totalSupplyBefore < totalSupplyAfter <=> totalAssetsBefore < totalAssetsAfter , "Strong monotonicity doesn't hold."; + assert (receiver != currentContract) => (totalAssetsBefore <= totalAssetsAfter <=> totalSupplyBefore <= totalSupplyAfter), "Monotonicity doesn't hold."; +} + + +invariant totalAssetsZeroImpliesTotalSupplyZero() + totalAssets() == 0 => totalSupply() == 0 + { + + preserved { + requireInvariant sumOfBalancesEqualsTotalSupply; + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupply; + } +} + +invariant sumOfBalancesEqualsTotalSupply() + sumOfBalances == to_mathint(totalSupply()); + +ghost mathint sumOfBalances { + init_state axiom sumOfBalances == 0; +} + +hook Sstore balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalances = sumOfBalances + newValue - oldValue; +} +hook Sload uint256 value balanceOf[KEY address auser] STORAGE { + //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to hold. + require to_mathint(value) <= sumOfBalances; +} + +invariant sumOfBalancesEqualsTotalSupplyERC20() + sumOfBalancesERC20 == to_mathint(_ERC20.totalSupply()); + +ghost mathint sumOfBalancesERC20 { + init_state axiom sumOfBalancesERC20 == 0; +} + +hook Sstore _ERC20.balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; + userBalance = newValue; +} + +hook Sload uint256 value _ERC20.balanceOf[KEY address auser] STORAGE { + //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to already hold. + require to_mathint(value) <= sumOfBalancesERC20; +} + +invariant singleUserBalanceSmallerThanTotalSupply() + userBalance <= sumOfBalancesERC20; + +ghost mathint userBalance { + init_state axiom userBalance == 0; +} + +//Current results: https://prover.certora.com/output/53900/be641a64bf1d4660abfd9cce3bcaefbc?anonymousKey=8cb85092656a9f2039cbf14cb47f6a2576b9c91f \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec new file mode 100644 index 0000000..7658476 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec @@ -0,0 +1,89 @@ + +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. +using ERC20 as __ERC20; + +//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupply; + + +methods{ + function balanceOf(address) external returns uint256 envfree; + function convertToAssets(uint256) external returns uint256 envfree; + function convertToShares(uint256) external returns uint256 envfree; + function maxDeposit(address) external returns uint256 envfree; + function maxMint(address) external returns uint256 envfree; + function maxRedeem(address) external returns uint256 envfree; + function maxWithdraw(address) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function totalSupply() external returns uint256 envfree; +} + +definition nonReveritngFunction(method f) returns bool = + f.selector == sig:convertToAssets(uint256).selector + || f.selector == sig:convertToShares(uint256).selector + || f.selector == sig:totalAssets().selector + || f.selector == sig:asset().selector + || f.selector == sig:maxDeposit(address).selector + || f.selector == sig:maxMint(address).selector + || f.selector == sig:maxRedeem(address).selector + || f.selector == sig:maxWithdraw(address).selector; + + +rule mustNotRevertProps(method f) filtered {f -> nonReveritngFunction(f)}{ + env e; + + require(e.msg.value == 0); + safeAssumptions(); + bool res = callMethodsWithParamenter(e, f); + assert res == false, "Method ${f} reverted."; +} + +function callMethodsWithParamenter(env e, method f) returns bool { + uint256 amount; + address addr; + if(f.selector == sig:convertToAssets(uint256).selector){ + //Reasonable assumptions for convertToAssets: No overflow on multiplication and no devision by 0. + require (amount * totalAssets() <= max_uint256); + require (totalSupply() > 0); + + convertToAssets@withrevert(amount); + return lastReverted; + } else if(f.selector == sig:convertToShares(uint256).selector){ + //Reasonable assumptions for convertToShare: No overflow on multiplication and no devision by 0. + require (amount * totalSupply() <= max_uint256); + require (totalAssets() > 0); + + convertToShares@withrevert(amount); + return lastReverted; + } else if(f.selector == sig:maxWithdraw(address).selector){ + + //Reasonable assumptions for convertToAssets: No overflow on multiplication and no devision by 0. + require (balanceOf(addr) * totalAssets() <= max_uint256); + require (totalSupply() > 0); + + maxWithdraw@withrevert(addr); + return lastReverted; + } else if(f.selector == sig:maxDeposit(address).selector){ + maxDeposit@withrevert(addr); + return lastReverted; + } else if(f.selector == sig:maxMint(address).selector){ + maxMint@withrevert(addr); + return lastReverted; + } else if(f.selector == sig:maxRedeem(address).selector){ + maxRedeem@withrevert(addr); + return lastReverted; + } else if(f.selector == sig:totalAssets().selector || f.selector == sig:asset().selector){ + calldataarg args; + f@withrevert(e, args); + return lastReverted; + } + //Should be unreachable. + return true; +} + +//Current Results: https://prover.certora.com/output/53900/91ecbf874cc948acae93970617b46246?anonymousKey=34cd5488963f665fc6cbfc19d831c1206d1cce19 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec new file mode 100644 index 0000000..7a9d53b --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec @@ -0,0 +1,120 @@ +using ERC20 as _ERC20; + +methods { + function _ERC20.balanceOf(address) external returns uint256 envfree; + function allowance(address, address) external returns uint256 envfree; + function balanceOf(address) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; +} + +/** +* Special case for when e.msg.sender == owner. +* 1. Third party `withdraw()` calls must update the msg.sender's allowance +* 2. withdraw() must allow proxies to withdraw tokens on behalf of the owner using share token approvals +* 3. Check that is doesn't revert. +*/ +rule ownerWithdrawal(uint256 assets, address receiver, address owner){ + env e; + require(e.msg.sender == owner); + + uint256 allowanceBefore = allowance(owner, e.msg.sender); + withdraw@withrevert(e, assets, receiver, owner); + uint256 allowanceAfter = allowance(owner, e.msg.sender); + assert allowanceAfter == allowanceBefore; + assert lastReverted == false; +} + + +//Third party `withdraw()` calls must update the msg.sender's allowance +//withdraw() must allow proxies to withdraw tokens on behalf of the owner using share token approvals +rule thirdPartyWithdrawal(uint256 assets, address receiver, address owner){ + env e; + require(e.msg.sender != owner); + + uint256 allowanceBefore = allowance(owner, e.msg.sender); + uint256 shares = previewWithdraw(assets); + + withdraw(e, assets, receiver, owner); + + uint256 allowanceAfter = allowance(owner, e.msg.sender); + assert allowanceAfter <= allowanceBefore; + assert shares <= allowanceBefore; +} + +//Third parties must not be able to withdraw() tokens on an owner's behalf without a token approval +rule thirdPartyWithdrawalRevertCase(uint256 assets, address receiver, address owner){ + env e; + uint256 allowanceBefore = allowance(owner, e.msg.sender); + uint256 shares = previewWithdraw(assets); + + require shares > allowanceBefore; + //If e.msg.sender is the owner, no allowance is required, see rule ownerWithdrawal + require e.msg.sender != owner; + + withdraw@withrevert(e, assets, receiver, owner); + + bool withdrawReverted = lastReverted; + + assert withdrawReverted, "withdraw does not revert when no allowance provided."; +} + + + +/** +* Special case for when e.msg.sender == owner. +* 1. Third party `redeem()` calls must update the msg.sender's allowance +* 2. redeem() must allow proxies to redeem shares on behalf of the owner using share token approvals +* 3. Check that is doesn't revert. +*/ +rule ownerRedeem(uint256 shares, address receiver, address owner){ + env e; + require(e.msg.sender == owner); + + uint256 allowanceBefore = allowance(owner, e.msg.sender); + redeem@withrevert(e, shares, receiver, owner); + uint256 allowanceAfter = allowance(owner, e.msg.sender); + assert allowanceAfter == allowanceBefore; + assert lastReverted == false; +} + + +//Third party `redeem()` calls must update the msg.sender's allowance +//redeem() must allow proxies to withdraw tokens on behalf of the owner using share token approvals +rule thirdPartyRedeem(uint256 shares, address receiver, address owner){ + env e; + require(e.msg.sender != owner); + + uint256 allowanceBefore = allowance(owner, e.msg.sender); + uint256 assets = previewRedeem(shares); + + redeem(e, shares, receiver, owner); + + uint256 allowanceAfter = allowance(owner, e.msg.sender); + assert allowanceAfter <= allowanceBefore; + assert shares <= allowanceBefore; +} + +//Third parties must not be able to redeem() tokens on an owner's behalf without a token approval +rule thirdPartyRedeemRevertCase(uint256 shares, address receiver, address owner){ + env e; + uint256 allowanceBefore = allowance(owner, e.msg.sender); + uint256 assets = previewRedeem(shares); + + require shares > allowanceBefore; + //If e.msg.sender is the owner, no allowance is required, see rule ownerWithdrawal + require e.msg.sender != owner; + + redeem@withrevert(e, shares, receiver, owner); + + bool redeemReverted = lastReverted; + + assert redeemReverted, "redeem does not revert when no allowance provided."; +} + + +invariant balanceOfERC20EqualToTotalAsset() + totalAssets() == _ERC20.balanceOf(currentContract); + +//Current results: https://prover.certora.com/output/53900/69d41bd6444149c1870a74cd96f8588b?anonymousKey=97da89d85f8dcdb2bef2f2cd5349b1e5342ada40 diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec new file mode 100644 index 0000000..d3421a1 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -0,0 +1,83 @@ +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. +using ERC20 as __ERC20; + +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupply; + + +methods{ + function balanceOf(address) external returns uint256 envfree; + function decimals() external returns uint8 envfree; + function totalAssets() external returns uint256 envfree; + function totalSupply() external returns uint256 envfree; +} + +rule inverseDepositRedeemInFavourForVault(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_owner){ + env e; + safeAssumptions(); + + uint256 shares = deposit(e, assets, deposit_receiver); + uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); + + assert assets >= redeemedAssets, "User cannot gain assets using deposit / redeem combination."; +} + +rule inverseRedeemDepositInFavourForVault(uint256 shares, address deposit_receiver, address redeem_receiver, address redeem_owner){ + env e; + safeAssumptions(); + + uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); + uint256 depositedShares = deposit(e, redeemedAssets, deposit_receiver); + + assert shares >= depositedShares, "User cannot gain shares using deposit / redeem combination."; +} + +rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + safeAssumptions(); + + uint256 assets = mint(e, shares, mint_receiver); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} + +rule inverseWithdrawMintInFavourForVault(uint256 assets, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + safeAssumptions(); + + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + uint256 mintedAssets = mint(e, withdrawnShares, mint_receiver); + + assert assets >= mintedAssets, "User cannot gain assets using deposit / redeem combination."; +} + + +//TODO: Not sure if this is even a valid property: The rule fails. +rule redeemInOneTransactionIsPreferable(address user, address receiver, uint256 s1, uint256 s2) { + env e; + + safeAssumptions(); + uint256 shares = require_uint256(s1 + s2); + + //The below requires have been added to find more intuitive counter examples. + require(e.msg.sender != currentContract); + require(e.msg.sender != user); + require(e.msg.sender != receiver); + require(user != receiver); + require(totalAssets() >= totalSupply()); + + storage init = lastStorage; + + mathint redeemed1a = redeem(e, s1, receiver, user); + mathint redeemed1b = redeem(e, s2, receiver, user); + mathint redeemed2 = redeem(e, shares, receiver, user) at init; + + assert(redeemed2 <= redeemed1a + redeemed1b); +} + +//Current results: https://prover.certora.com/output/53900/c3da8b5edf6f4561bf13bc12a8f73afc?anonymousKey=30965a0f354b3452eb87d8a35272c68121ce9015 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec new file mode 100644 index 0000000..6bc385e --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec @@ -0,0 +1,77 @@ +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. +using ERC20 as __ERC20; + +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupply; + + +methods { + function allowance(address, address) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function decimals() external returns uint8 envfree; + function __ERC20.decimals() external returns uint8 envfree; + function totalSupply() external returns uint256 envfree; +} + + +//deposit must increase totalSupply +rule depositMustIncreaseTotalSupply(uint256 assets, address user){ + safeAssumptions(); + + uint256 totalSupplyBefore = totalSupply(); + env e; + deposit(e, assets, user); + uint256 totalSupplyAfter = totalSupply(); + assert totalSupplyAfter >= totalSupplyBefore, "Total supply must increase when deposit is called."; +} + +//mint must increase totalAssets +rule mintMustIncreaseTotalAssets(uint256 shares, address user){ + safeAssumptions(); + + uint256 totalAssetsBefore = totalAssets(); + env e; + mint(e, shares, user); + uint256 totalAssetsAfter = totalAssets(); + assert totalAssetsAfter >= totalAssetsBefore, "Total assets must increase when mint is called."; +} + +//withdraw must decrease totalAssets +rule withdrawMustDecreaseTotalSupply(uint256 assets, address receiver, address owner){ + safeAssumptions(); + + uint256 totalSupplyBefore = totalSupply(); + env e; + + withdraw(e, assets, receiver, owner); + uint256 totalSupplyAfter = totalSupply(); + assert totalSupplyAfter <= totalSupplyBefore, "Total supply must decrease when withdraw is called."; +} + +//redeem must decrease totalAssets +rule redeemMustDecreaseTotalAssets(uint256 shares, address receiver, address owner){ + safeAssumptions(); + + uint256 totalAssetsBefore = totalAssets(); + env e; + redeem(e, shares, receiver, owner); + uint256 totalAssetsAfter = totalAssets(); + assert totalAssetsAfter <= totalAssetsBefore, "Total assets must decrease when redeem is called."; +} + +//`decimals()` should be larger than or equal to `asset.decimals()` +rule decimalsOfUnderlyingVaultShouldBeLarger(uint256 shares, address receiver, address owner){ + //TODO: Rule fails. The method call to decimals returns a HAVOC'd value. Still the solver should be able to reason that ERC4626.decimals == ERC20.decimals as of the call to the super constructor. Don't understand why. + safeAssumptions(); + + uint8 assetDecimals = __ERC20.decimals(); + uint8 decimals = decimals(); + + assert decimals >= assetDecimals, "Decimals of underlying ERC20 should be larger than ERC4626 decimals."; +} + +//Current results: https://prover.certora.com/output/53900/07ab0436f2424a539eb23cd32429c81c?anonymousKey=8ca00e5e8b0af8d70a7f6bb862e3ab7332c10d92 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/Properties.md b/lesson4_reading/erc4626/src/certora/specs/Properties.md new file mode 100644 index 0000000..8d37a61 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/Properties.md @@ -0,0 +1,129 @@ +MustNotRevertProps +--- +* `convertToAssets()` must not revert for reasonable values +* `convertToShares()` must not revert for reasonable values + +> Comment Johannes: What's meant by reasonable values here? There are ways to make both revert, when `totalSupply * shares|assets` overflows. + +* `asset()` must not revert +* `totalAssets()` must not revert +* `maxDeposit()` must not revert +* `maxMint()` must not revert +* `maxRedeem()` must not revert +* `maxWithdraw()` must not revert + +> Comment Johannes: `maxWithdraw` reverts in an integer overflow case as it depends on `convertToAssets()`. If that spec is correct, it's a bug. + +FunctionalAccountingProps +--- +> Implementation see rule depositProperties +* `deposit()` must deduct assets from the owner +* `deposit()` must credit shares to the receiver +* `deposit()` must mint greater than or equal to the number of shares predicted by `previewDeposit()` +> Implementation see rule mintProperties +* `mint()` must deduct assets from the owner +* `mint()` must credit shares to the receiver +* `mint()` must consume less than or equal to the number of assets predicted by `previewMint()` +> Implementation see rule withdrawProperties +* `withdraw()` must deduct shares from the owner +* `withdraw()` must credit assets to the receiver +* `withdraw()` must deduct less than or equal to the number of shares predicted by `previewWithdraw()` +> Implementation see rule redeemProperties +* `redeem()` must deduct shares from the owner +* `redeem()` must credit assets to the receiver +* `redeem()` must credit greater than or equal to the number of assets predicted by `previewRedeem()` + +RedeemUsingApprovalProps +--- +> All properties with withdraw and redeem implemented. +* `withdraw()` must allow proxies to withdraw tokens on behalf of the owner using share token approvals +* `redeem()` must allow proxies to redeem shares on behalf of the owner using share token approvals +* Third party `withdraw()` calls must update the msg.sender's allowance +* Third party `redeem()` calls must update the msg.sender's allowance +* Third parties must not be able to `withdraw()` tokens on an owner's behalf without a token approval +* Third parties must not be able to `redeem()` shares on an owner's behalf without a token approval + +SenderIndependentProps +--- +> declaring methods env free checks this automatically for us. Otherwise one _could_ take two different env e1, env e2 and show that the return value of the function calls is equal for any environment. +* `maxDeposit()` must assume the receiver/sender has infinite assets +* `maxMint()` must assume the receiver/sender has infinite assets +* `previewMint()` must not account for msg.sender asset balance +* `previewDeposit()` must not account for msg.sender asset balance +* `previewWithdraw()` must not account for msg.sender share balance +* `previewRedeem()` must not account for msg.sender share balance + + + +RoundingProps +--- +> Show that storage doesn't change when using these function. (i.e. check that all view function are actually view) +* Shares may never be minted for free using: + * `previewDeposit()` + * `previewMint()` + * `convertToShares()` + +* Tokens may never be withdrawn for free using: + * `previewWithdraw()` + * `previewRedeem()` + * `convertToAssets()` + +> Solve a rule totalMonotonicty +* Shares may never be minted for free using: + * `deposit()` + * `mint()` + +* Tokens may never be withdrawn for free using: + * `withdraw()` + * `redeem()` + +SecurityProps +--- +* `decimals()` should be larger than or equal to `asset.decimals()` + +> Added specification in file `ERC4626-InflationAttack.spec`` +* Accounting system must not be vulnerable to share price inflation attacks + +> Johannes: Added 4 rules to show the below. +* `deposit/mint` must increase `totalSupply/totalAssets` +* `withdraw/redeem` must decrease `totalSupply/totalAssets` + + +* `previewDeposit()` must not account for vault specific/user/global limits +* `previewMint()` must not account for vault specific/user/global limits +* `previewWithdraw()` must not account for vault specific/user/global limits +* `previewRedeem()` must not account for vault specific/user/global limits + + +Properties that may not be testable +--- +* Note that any unfavorable discrepancy between `convertToShares` and `previewDeposit` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing. +* Whether a given method is inclusive of withdraw/deposit fees + + + + +Other Properties +------ +* Deposit increases the number of shares by mint. Shares minted should be proportional to the increase of the balance of the vault. + ``` + a: amount to deposit + B: Balance of vault before deposit + s: shares to mint + T: Total shares before minting + Then it holds: (a+B)/B = (s+T)/T => s = aT/B + ``` + Source https://www.youtube.com/watch?v=k7WNibJOBXE + +* When withdrawing, the shares burned must be proportional to the total balance of the vault + ``` + a: amount to withdraw + B: Balance of vault before deposit + s: shares to burn + T: Total shares before burning + Then it holds: (B-a)/B = (T-s)/T => s = aT/B + ``` + Source https://www.youtube.com/watch?v=k7WNibJOBXE +* ERC-4626 Inflation attack: Can we model a rule that "executes" the inflation attack? What happens if we change the analyzed contract to use standard division instead of `FixedPointMathLib.sol`? Does the rule detect it? + + Source https://tienshaoku.medium.com/eip-4626-inflation-sandwich-attack-deep-dive-and-how-to-solve-it-9e3e320cc3f1 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/libs/FixedPointMathLib.sol b/lesson4_reading/erc4626/src/libs/FixedPointMathLib.sol new file mode 100644 index 0000000..c989b75 --- /dev/null +++ b/lesson4_reading/erc4626/src/libs/FixedPointMathLib.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Arithmetic library with operations for fixed-point numbers. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) +/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) +library FixedPointMathLib { + /*////////////////////////////////////////////////////////////// + SIMPLIFIED FIXED POINT OPERATIONS + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant MAX_UINT256 = 2**256 - 1; + + uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. + + function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. + } + + function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. + } + + function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. + } + + function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. + } + + /*////////////////////////////////////////////////////////////// + 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) + } + // Case above: 2*8 mod 10 = 16 mod 10 == 6 mod 10 = 2*3 mod 10. 8 != 3 => revert. + // Case below: 2 <= 10 / 8 which is false => revert + // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) + /*if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, 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 rpow( + uint256 x, + uint256 n, + uint256 scalar + ) internal pure returns (uint256 z) { + assembly { + switch x + case 0 { + switch n + case 0 { + // 0 ** 0 = 1 + z := scalar + } + default { + // 0 ** n = 0 + z := 0 + } + } + default { + switch mod(n, 2) + case 0 { + // If n is even, store scalar in z for now. + z := scalar + } + default { + // If n is odd, store x in z for now. + z := x + } + + // Shifting right by 1 is like dividing by 2. + let half := shr(1, scalar) + + for { + // Shift n right by 1 before looping to halve it. + n := shr(1, n) + } n { + // Shift n right by 1 each iteration to halve it. + n := shr(1, n) + } { + // Revert immediately if x ** 2 would overflow. + // Equivalent to iszero(eq(div(xx, x), x)) here. + if shr(128, x) { + revert(0, 0) + } + + // Store x squared. + let xx := mul(x, x) + + // Round to the nearest number. + let xxRound := add(xx, half) + + // Revert if xx + half overflowed. + if lt(xxRound, xx) { + revert(0, 0) + } + + // Set x to scaled xxRound. + x := div(xxRound, scalar) + + // If n is even: + if mod(n, 2) { + // Compute z * x. + let zx := mul(z, x) + + // If z * x overflowed: + if iszero(eq(div(zx, x), z)) { + // Revert if x is non-zero. + if iszero(iszero(x)) { + revert(0, 0) + } + } + + // Round to the nearest number. + let zxRound := add(zx, half) + + // Revert if zx + half overflowed. + if lt(zxRound, zx) { + revert(0, 0) + } + + // Return properly scaled zxRound. + z := div(zxRound, scalar) + } + } + } + } + } + + /*////////////////////////////////////////////////////////////// + GENERAL NUMBER UTILITIES + //////////////////////////////////////////////////////////////*/ + + function sqrt(uint256 x) internal pure returns (uint256 z) { + assembly { + // Start off with z at 1. + z := 1 + + // Used below to help find a nearby power of 2. + let y := x + + // Find the lowest power of 2 that is at least sqrt(x). + if iszero(lt(y, 0x100000000000000000000000000000000)) { + y := shr(128, y) // Like dividing by 2 ** 128. + z := shl(64, z) // Like multiplying by 2 ** 64. + } + if iszero(lt(y, 0x10000000000000000)) { + y := shr(64, y) // Like dividing by 2 ** 64. + z := shl(32, z) // Like multiplying by 2 ** 32. + } + if iszero(lt(y, 0x100000000)) { + y := shr(32, y) // Like dividing by 2 ** 32. + z := shl(16, z) // Like multiplying by 2 ** 16. + } + if iszero(lt(y, 0x10000)) { + y := shr(16, y) // Like dividing by 2 ** 16. + z := shl(8, z) // Like multiplying by 2 ** 8. + } + if iszero(lt(y, 0x100)) { + y := shr(8, y) // Like dividing by 2 ** 8. + z := shl(4, z) // Like multiplying by 2 ** 4. + } + if iszero(lt(y, 0x10)) { + y := shr(4, y) // Like dividing by 2 ** 4. + z := shl(2, z) // Like multiplying by 2 ** 2. + } + if iszero(lt(y, 0x8)) { + // Equivalent to 2 ** z. + z := shl(1, z) + } + + // Shifting right by 1 is like dividing by 2. + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + + // Compute a rounded down version of z. + let zRoundDown := div(x, z) + + // If zRoundDown is smaller, use it. + if lt(zRoundDown, z) { + z := zRoundDown + } + } + } +} diff --git a/lesson4_reading/erc4626/src/tokens/ERC20.sol b/lesson4_reading/erc4626/src/tokens/ERC20.sol new file mode 100644 index 0000000..99217dd --- /dev/null +++ b/lesson4_reading/erc4626/src/tokens/ERC20.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Modern and gas efficient ERC20 implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) +/// @author Modified from Uniswap +/// (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) +/// @dev Do not manually set balances without updating totalSupply, as the sum of all +/// user balances must not exceed it. +contract ERC20 { + // EVENTS + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + // METADATA STORAGE + + string public name; + + string public symbol; + + uint8 public immutable decimals; + + // ERC20 STORAGE + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + // CONSTRUCTOR + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals + ) { + name = _name; + symbol = _symbol; + decimals = _decimals; + } + + // ERC20 LOGIC + + function approve(address spender, uint256 amount) public virtual returns (bool) { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + + return true; + } + + function transfer(address to, uint256 amount) public virtual returns (bool) { + balanceOf[msg.sender] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + return true; + } + + // INTERNAL MINT/BURN LOGIC + + function _mint(address to, uint256 amount) internal virtual { + totalSupply += amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(address(0), to, amount); + } + + function _burn(address from, uint256 amount) internal virtual { + balanceOf[from] -= amount; + + // Cannot underflow because a user's balance + // will never be larger than the total supply. + unchecked { + totalSupply -= amount; + } + + emit Transfer(from, address(0), amount); + } +} From 2370cc34b24e7b0d86b74d48e8ebc0e9d2d30dd6 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 12 Sep 2023 09:53:01 +0200 Subject: [PATCH 002/125] Adding missing library --- .../erc4626/src/libs/SafeTransferLib.sol | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 lesson4_reading/erc4626/src/libs/SafeTransferLib.sol diff --git a/lesson4_reading/erc4626/src/libs/SafeTransferLib.sol b/lesson4_reading/erc4626/src/libs/SafeTransferLib.sol new file mode 100644 index 0000000..d08d1b8 --- /dev/null +++ b/lesson4_reading/erc4626/src/libs/SafeTransferLib.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "../tokens/ERC20.sol"; + +/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) +/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. +/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. +library SafeTransferLib { + /*////////////////////////////////////////////////////////////// + ETH OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function safeTransferETH(address to, uint256 amount) internal { + bool success; + + assembly { + // Transfer the ETH and store if it succeeded or not. + success := call(gas(), to, amount, 0, 0, 0, 0) + } + + require(success, "ETH_TRANSFER_FAILED"); + } + + /*////////////////////////////////////////////////////////////// + ERC20 OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function safeTransferFrom( + ERC20 token, + address from, + address to, + uint256 amount + ) internal { + bool success; + + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument. + mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) + ) + } + + require(success, "TRANSFER_FROM_FAILED"); + } + + function safeTransfer( + ERC20 token, + address to, + uint256 amount + ) internal { + bool success; + + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "TRANSFER_FAILED"); + } + + function safeApprove( + ERC20 token, + address to, + uint256 amount + ) internal { + bool success; + + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "APPROVE_FAILED"); + } +} From de8b419ef6dad475677cb30874c406574c9e50ce Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 12 Sep 2023 09:55:02 +0200 Subject: [PATCH 003/125] Adding minimal OpenZeppelin code Extracted from https://github.com/OpenZeppelin/openzeppelin-contracts/commit/9ef69c03d13230aeff24d91cb54c9d24c4de7c8b --- .../contracts/interfaces/IERC4626.sol | 230 ++++++++++ .../contracts/interfaces/draft-IERC6093.sol | 160 +++++++ .../contracts/mocks/token/ERC20Mock.sol | 16 + .../contracts/mocks/token/ERC4626Mock.sol | 17 + .../contracts/token/ERC20/ERC20.sol | 372 ++++++++++++++++ .../contracts/token/ERC20/IERC20.sol | 79 ++++ .../token/ERC20/extensions/ERC4626.sol | 286 ++++++++++++ .../token/ERC20/extensions/IERC20Metadata.sol | 26 ++ .../token/ERC20/extensions/IERC20Permit.sol | 60 +++ .../contracts/token/ERC20/utils/SafeERC20.sol | 140 ++++++ .../contracts/utils/Address.sol | 159 +++++++ .../contracts/utils/Context.sol | 24 + .../contracts/utils/math/Math.sol | 414 ++++++++++++++++++ 13 files changed, 1983 insertions(+) create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/IERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/draft-IERC6093.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/IERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Metadata.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Permit.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/utils/SafeERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Address.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Context.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/IERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/IERC4626.sol new file mode 100644 index 0000000..e1b778e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + 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 + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/draft-IERC6093.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/draft-IERC6093.sol new file mode 100644 index 0000000..c38379a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/draft-IERC6093.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @dev Standard ERC20 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + */ +interface IERC20Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC20InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC20InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. + * @param spender Address that may be allowed to operate on tokens without being their owner. + * @param allowance Amount of tokens a `spender` is allowed to operate with. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC20InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `spender` to be approved. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC20InvalidSpender(address spender); +} + +/** + * @dev Standard ERC721 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + */ +interface IERC721Errors { + /** + * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. + * Used in balance queries. + * @param owner Address of the current owner of a token. + */ + error ERC721InvalidOwner(address owner); + + /** + * @dev Indicates a `tokenId` whose `owner` is the zero address. + * @param tokenId Identifier number of a token. + */ + error ERC721NonexistentToken(uint256 tokenId); + + /** + * @dev Indicates an error related to the ownership over a particular token. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param tokenId Identifier number of a token. + * @param owner Address of the current owner of a token. + */ + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC721InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC721InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param tokenId Identifier number of a token. + */ + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC721InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC721InvalidOperator(address operator); +} + +/** + * @dev Standard ERC1155 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. + */ +interface IERC1155Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + * @param tokenId Identifier number of a token. + */ + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC1155InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC1155InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param owner Address of the current owner of a token. + */ + error ERC1155MissingApprovalForAll(address operator, address owner); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC1155InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC1155InvalidOperator(address operator); + + /** + * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + * Used in batch transfers. + * @param idsLength Length of the array of token identifiers + * @param valuesLength Length of the array of token amounts + */ + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol new file mode 100644 index 0000000..39ab129 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor() ERC20("ERC20Mock", "E20M") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol new file mode 100644 index 0000000..22ac5e8 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +contract ERC4626Mock is ERC4626 { + constructor(address underlying) ERC20("ERC4626Mock", "E4626M") ERC4626(IERC20(underlying)) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol new file mode 100644 index 0000000..8eeb314 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * The default value of {decimals} is 18. To change this, you should override + * this function so it returns a different value. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { + mapping(address account => uint256) private _balances; + + mapping(address account => mapping(address spender => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(address to, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, value); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, value); + _transfer(from, to, value); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `requestedDecrease`. + * + * NOTE: Although this function is designed to avoid double spending with {approval}, + * it can still be frontrunned, preventing any attempt of allowance reduction. + */ + function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance < requestedDecrease) { + revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + unchecked { + _approve(owner, spender, currentAllowance - requestedDecrease); + } + + return true; + } + + /** + * @dev Moves a `value` amount of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(address from, address to, uint256 value) internal { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(from, to, value); + } + + /** + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is + * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. + * + * Emits a {Transfer} event. + */ + function _update(address from, address to, uint256 value) internal virtual { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; + } else { + uint256 fromBalance = _balances[from]; + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[from] = fromBalance - value; + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[to] += value; + } + } + + emit Transfer(from, to, value); + } + + /** + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _mint(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * Relies on the `_update` mechanism. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead + */ + function _burn(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } + _update(account, address(0), value); + } + + /** + * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address owner, address spender, uint256 value) internal { + _approve(owner, spender, value, true); + } + + /** + * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. + * + * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by + * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any + * `Approval` event during `transferFrom` operations. + * + * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true + * using the following override: + * ``` + * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { + * super._approve(owner, spender, value, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _allowances[owner][spender] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/IERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/IERC20.sol new file mode 100644 index 0000000..77ca716 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol new file mode 100644 index 0000000..adc4f66 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; +import {IERC4626} from "../../../interfaces/IERC4626.sol"; +import {Math} from "../../../utils/math/Math.sol"; + +/** + * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in + * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + * + * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for + * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends + * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this + * contract and not the "assets" token which is an independent contract. + * + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial + * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * verifying the amount received is as expected, using a wrapper that performs these checks such as + * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + */ +abstract contract ERC4626 is ERC20, IERC4626 { + using Math for uint256; + + IERC20 private immutable _asset; + uint8 private immutable _underlyingDecimals; + + /** + * @dev Attempted to deposit more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @dev Attempted to mint more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @dev Attempted to withdraw more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @dev Attempted to redeem more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + + /** + * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + */ + constructor(IERC20 asset_) { + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _underlyingDecimals = success ? assetDecimals : 18; + _asset = asset_; + } + + /** + * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. + */ + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( + abi.encodeCall(IERC20Metadata.decimals, ()) + ); + if (success && encodedDecimals.length >= 32) { + uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + if (returnedDecimals <= type(uint8).max) { + return (true, uint8(returnedDecimals)); + } + } + return (false, 0); + } + + /** + * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This + * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the + * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + * + * See {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _underlyingDecimals + _decimalsOffset(); + } + + /** @dev See {IERC4626-asset}. */ + function asset() public view virtual returns (address) { + return address(_asset); + } + + /** @dev See {IERC4626-totalAssets}. */ + function totalAssets() public view virtual returns (uint256) { + return _asset.balanceOf(address(this)); + } + + /** @dev See {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxMint}. */ + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf(owner); + } + + /** @dev See {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + + uint256 shares = previewDeposit(assets); + _deposit(_msgSender(), receiver, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-mint}. + * + * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. + * In this case, the shares will be minted without requiring any assets to be deposited. + */ + function mint(uint256 shares, address receiver) public virtual returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + + uint256 assets = previewMint(shares); + _deposit(_msgSender(), receiver, assets, shares); + + return assets; + } + + /** @dev See {IERC4626-withdraw}. */ + function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + + uint256 shares = previewWithdraw(assets); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-redeem}. */ + function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + + uint256 assets = previewRedeem(shares); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return assets; + } + + /** + * @dev Internal conversion function (from assets to shares) with support for rounding direction. + */ + function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { + return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); + } + + /** + * @dev Internal conversion function (from shares to assets) with support for rounding direction. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + } + + /** + * @dev Deposit/mint common workflow. + */ + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the + // assets are transferred and before the shares are minted, which is a valid state. + // slither-disable-next-line reentrancy-no-eth + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _mint(receiver, shares); + + emit Deposit(caller, receiver, assets, shares); + } + + /** + * @dev Withdraw/redeem common workflow. + */ + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + + // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the + // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the + // shares are burned and after the assets are transferred, which is a valid state. + _burn(owner, shares); + SafeERC20.safeTransfer(_asset, receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + function _decimalsOffset() internal view virtual returns (uint8) { + return 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Metadata.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Metadata.sol new file mode 100644 index 0000000..9056e34 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Permit.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Permit.sol new file mode 100644 index 0000000..2370410 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/utils/SafeERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/utils/SafeERC20.sol new file mode 100644 index 0000000..fcdbbae --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/utils/SafeERC20.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; +import {IERC20Permit} from "../extensions/IERC20Permit.sol"; +import {Address} from "../../../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + /** + * @dev An operation with an ERC20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + */ + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); + } + + /** + * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 oldAllowance = token.allowance(address(this), spender); + forceApprove(token, spender, oldAllowance + value); + } + + /** + * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { + unchecked { + uint256 currentAllowance = token.allowance(address(this), spender); + if (currentAllowance < requestedDecrease) { + revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + forceApprove(token, spender, currentAllowance - requestedDecrease); + } + } + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. + */ + function forceApprove(IERC20 token, address spender, uint256 value) internal { + bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); + + if (!_callOptionalReturnBool(token, approvalCall)) { + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); + _callOptionalReturn(token, approvalCall); + } + } + + /** + * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. + * Revert on invalid signature. + */ + function safePermit( + IERC20Permit token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + uint256 nonceBefore = token.nonces(owner); + token.permit(owner, spender, value, deadline, v, r, s); + uint256 nonceAfter = token.nonces(owner); + if (nonceAfter != nonceBefore + 1) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data); + if (returndata.length != 0 && !abi.decode(returndata, (bool))) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false + // and not revert is the subcall reverts. + + (bool success, bytes memory returndata) = address(token).call(data); + return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Address.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Address.sol new file mode 100644 index 0000000..fd22b05 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Address.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error AddressInsufficientBalance(address account); + + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedInnerCall(); + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) { + revert AddressInsufficientBalance(address(this)); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert FailedInnerCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {FailedInnerCall} error. + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + if (address(this).balance < value) { + revert AddressInsufficientBalance(address(this)); + } + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an + * unsuccessful call. + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) internal view returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); + } + return returndata; + } + } + + /** + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {FailedInnerCall} error. + */ + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. + */ + function _revert(bytes memory returndata) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert FailedInnerCall(); + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Context.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Context.sol new file mode 100644 index 0000000..25e1159 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol new file mode 100644 index 0000000..17ce4c8 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) + * with further edits by Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. + // See https://cs.stackexchange.com/q/138556/92363. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + } + } + + /** + * @dev Return the log in base 2 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 10 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 256 of a positive value rounded towards zero. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + } + } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } +} From 60763f7b8be49dabca0e059252e60c58f7671046 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 12 Sep 2023 09:58:39 +0200 Subject: [PATCH 004/125] Adding config and rules adopted for Open Zeppelin --- .../certoraRun-FunctionalAccountingProps.conf | 14 ++++++ .../certoraRun-InflationAttack.conf | 19 ++++++++ .../certoraRun-MonotonicityInvariant.conf | 14 ++++++ .../certoraRun-MustNotRevertProps.conf | 14 ++++++ .../ERC4626-FunctionalAccountingProps.spec | 5 ++- .../specs/ERC4626-InflationAttack.spec | 7 +-- .../specs/ERC4626-MonotonicityInvariant.spec | 29 ++++++++++--- .../specs/ERC4626-MustNotRevertProps.spec | 43 ++++++++----------- .../certora/specs/ERC4626-RoundingProps.spec | 6 ++- .../certora/specs/ERC4626-SecurityProps.spec | 4 +- 10 files changed, 113 insertions(+), 42 deletions(-) create mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-FunctionalAccountingProps.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MonotonicityInvariant.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MustNotRevertProps.conf diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-FunctionalAccountingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-FunctionalAccountingProps.conf new file mode 100644 index 0000000..755fc12 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-FunctionalAccountingProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" + ], + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "solc": "solc8.20", + "verify": "ERC4626Mock:src/certora/specs/ERC4626-FunctionalAccountingProps.spec", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf new file mode 100644 index 0000000..fcb20f7 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf @@ -0,0 +1,19 @@ +{ + "files": [ + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" + ], + "verify": "ERC4626Mock:src/certora/specs/ERC4626-InflationAttack.spec", + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "solc": "solc8.20", + "prover_args": [ + "-numOfUnsatCores 1", + "-canonicalizeTAC false" + ], + "prover_version": "master", + "server": "staging", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MonotonicityInvariant.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MonotonicityInvariant.conf new file mode 100644 index 0000000..da84c04 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MonotonicityInvariant.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" + ], + "verify": "ERC4626Mock:src/certora/specs/ERC4626-MonotonicityInvariant.spec", + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "solc": "solc8.20", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626 OpenZeppelin", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MustNotRevertProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MustNotRevertProps.conf new file mode 100644 index 0000000..973ec47 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MustNotRevertProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" + ], + "verify": "ERC4626Mock:src/certora/specs/ERC4626-MustNotRevertProps.spec", + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "solc": "solc8.20", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec index 89bfdb0..356a0a0 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec @@ -1,7 +1,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to __ERC20 as of import that already declares _ERC20. -using ERC20 as __ERC20; +using ERC20Mock as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; @@ -127,4 +127,5 @@ rule redeemProperties(uint256 shares, address receiver, address owner){ assert assets >= previewAssets; } -//Current results: https://prover.certora.com/output/53900/7538cffeb6f5475580378e6b3f25cefd?anonymousKey=a51328c86e7ebd55e0a9bab660fb6dc241ef0cbd \ No newline at end of file +//Current results: https://prover.certora.com/output/53900/7538cffeb6f5475580378e6b3f25cefd?anonymousKey=a51328c86e7ebd55e0a9bab660fb6dc241ef0cbd//Current results: https://prover.certora.com/output/53900/9bff1a944136468fbd8ae1ec692542f2?anonymousKey=432f0b4234ca29fa80743d112d7579bf60c3440a +//Current results for Open Zeppelin: https://prover.certora.com/output/53900/82f230b8ac15469abeebc1dbcab64c80?anonymousKey=9cee98e777a96b8d5d26e4604b4a9ea1942d73b6 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec index f3348e2..6388da2 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec @@ -2,7 +2,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20 as __ERC20; +using ERC20Mock as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; @@ -43,7 +43,7 @@ rule simpleVersionOfVulnerableAttack(uint256 assets, address deposit_receiver, a uint256 shares = deposit(e, assets, deposit_receiver); - //In the inflationAttack there are 2 steps that we don't model here! + //In the inflationAttack there are 2 steps that we don't model here! This cannot really work.... uint256 receivedAssets = redeem(e, shares, redeem_receiver, redeem_ownver); assert(receivedAssets <= assets); @@ -159,4 +159,5 @@ rule vulnerableToInflationAttack(address attacker, address victim, address depos assert assetsAttackerPreAttack >= assetsAttackerPostAttack, "The attacker gained assets."; } -//Current results: https://prover.certora.com/output/53900/478fe72720584bc6af003f4f9bf6e4c9?anonymousKey=56d469faee96a177b669d70fe199a2e318e2c714 \ No newline at end of file +//Current results: https://prover.certora.com/output/53900/478fe72720584bc6af003f4f9bf6e4c9?anonymousKey=56d469faee96a177b669d70fe199a2e318e2c714 +//Current result on Open Zeppelin: https://prover.certora.com/output/53900/482a564d0bde481cbdfac0ce0702e1a5?anonymousKey=3346e1373095201ac1b0c3c0ae985c11b94e51a6 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec index 64f8309..cdbc598 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec @@ -1,5 +1,5 @@ -using ERC20 as _ERC20; +using ERC20Mock as _ERC20; methods { function totalSupply() external returns uint256 envfree; @@ -11,6 +11,19 @@ methods { function previewDeposit(uint256) external returns uint256 envfree; function previewRedeem(uint256) external returns uint256 envfree; function _ERC20.totalSupply() external returns uint256 envfree; + function ERC20._update(address from, address to, uint256 value) internal => updateStub(from, to, value); +} + +function updateStub(address from, address to ,uint256 amount) { + uint256 balanceOfTo = balanceOf(to); + uint256 balanceOfFrom = balanceOf(from); + uint256 totalSupply = totalSupply(); + //TODO: verify the assumptions by writing a rule for it. + if(to != from){ + require (balanceOf(to) == require_uint256(balanceOfTo + amount)); + require (balanceOf(from) == require_uint256(balanceOfFrom - amount)); + require (require_uint256(totalSupply + amount) == totalSupply()); + } } function safeAssumptions(){ @@ -51,7 +64,9 @@ rule assetAndShareMonotonicy(){ assert (receiver != currentContract) => (totalAssetsBefore <= totalAssetsAfter <=> totalSupplyBefore <= totalSupplyAfter), "Monotonicity doesn't hold."; } - +/** +* This invariant does not hold for OpenZeppelin. There is a public function mint that allows to increase totalSupply without increasing totalAssets! +*/ invariant totalAssetsZeroImpliesTotalSupplyZero() totalAssets() == 0 => totalSupply() == 0 { @@ -70,10 +85,10 @@ ghost mathint sumOfBalances { init_state axiom sumOfBalances == 0; } -hook Sstore balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { +hook Sstore _balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { sumOfBalances = sumOfBalances + newValue - oldValue; } -hook Sload uint256 value balanceOf[KEY address auser] STORAGE { +hook Sload uint256 value _balances[KEY address auser] STORAGE { //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to hold. require to_mathint(value) <= sumOfBalances; } @@ -85,12 +100,12 @@ ghost mathint sumOfBalancesERC20 { init_state axiom sumOfBalancesERC20 == 0; } -hook Sstore _ERC20.balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { +hook Sstore _ERC20._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; userBalance = newValue; } -hook Sload uint256 value _ERC20.balanceOf[KEY address auser] STORAGE { +hook Sload uint256 value _ERC20._balances[KEY address auser] STORAGE { //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to already hold. require to_mathint(value) <= sumOfBalancesERC20; } @@ -102,4 +117,4 @@ ghost mathint userBalance { init_state axiom userBalance == 0; } -//Current results: https://prover.certora.com/output/53900/be641a64bf1d4660abfd9cce3bcaefbc?anonymousKey=8cb85092656a9f2039cbf14cb47f6a2576b9c91f \ No newline at end of file +//Current results: https://prover.certora.com/output/53900/1c908f0eff9c42518fc6206e42152cd8/?anonymousKey=435376258db9ce72101337c61da0d6e95b45d02f \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec index 7658476..d42b185 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec @@ -1,16 +1,6 @@ -import "./ERC4626-MonotonicityInvariant.spec"; - -//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20 as __ERC20; - -//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? -use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupply; -use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupply; - +import "./ERC4626-MonotonicityInvariant.spec"; methods{ function balanceOf(address) external returns uint256 envfree; function convertToAssets(uint256) external returns uint256 envfree; @@ -23,6 +13,14 @@ methods{ function totalSupply() external returns uint256 envfree; } +//ERC4626-Monotonicity declares safeAssumptions(), why is it required to activeley declare the invariants used in safeAssumptions in this file? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupply; + + + definition nonReveritngFunction(method f) returns bool = f.selector == sig:convertToAssets(uint256).selector || f.selector == sig:convertToShares(uint256).selector @@ -36,7 +34,6 @@ definition nonReveritngFunction(method f) returns bool = rule mustNotRevertProps(method f) filtered {f -> nonReveritngFunction(f)}{ env e; - require(e.msg.value == 0); safeAssumptions(); bool res = callMethodsWithParamenter(e, f); @@ -47,25 +44,18 @@ function callMethodsWithParamenter(env e, method f) returns bool { uint256 amount; address addr; if(f.selector == sig:convertToAssets(uint256).selector){ - //Reasonable assumptions for convertToAssets: No overflow on multiplication and no devision by 0. - require (amount * totalAssets() <= max_uint256); - require (totalSupply() > 0); - + /**OZ: + We'd need to ensure that totalAssets() + 1 does not overflow....totalSupply() + 10**__decimalsOffset() doesn't overflow and the mulDiv doesn't either + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + } + */ convertToAssets@withrevert(amount); return lastReverted; } else if(f.selector == sig:convertToShares(uint256).selector){ - //Reasonable assumptions for convertToShare: No overflow on multiplication and no devision by 0. - require (amount * totalSupply() <= max_uint256); - require (totalAssets() > 0); - convertToShares@withrevert(amount); return lastReverted; } else if(f.selector == sig:maxWithdraw(address).selector){ - - //Reasonable assumptions for convertToAssets: No overflow on multiplication and no devision by 0. - require (balanceOf(addr) * totalAssets() <= max_uint256); - require (totalSupply() > 0); - maxWithdraw@withrevert(addr); return lastReverted; } else if(f.selector == sig:maxDeposit(address).selector){ @@ -86,4 +76,5 @@ function callMethodsWithParamenter(env e, method f) returns bool { return true; } -//Current Results: https://prover.certora.com/output/53900/91ecbf874cc948acae93970617b46246?anonymousKey=34cd5488963f665fc6cbfc19d831c1206d1cce19 \ No newline at end of file +//Current Results: https://prover.certora.com/output/53900/d309d19804ea430da22bfed21fa3dab2?anonymousKey=c8dc63e28bb1d5f2161f021b0332cbd4cbdfc442 +//Current results on Open Zeppelin https://prover.certora.com/output/53900/56ba04402c354671948812c6b58c59ec?anonymousKey=bf80f4cd10de6aebf76c0bea70f6c47e80278290 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index d3421a1..3bee5be 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -16,6 +16,8 @@ methods{ function totalSupply() external returns uint256 envfree; } +//TODO test this rule on OpenZeppelin with equality on assert. + rule inverseDepositRedeemInFavourForVault(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_owner){ env e; safeAssumptions(); @@ -57,7 +59,7 @@ rule inverseWithdrawMintInFavourForVault(uint256 assets, address mint_receiver, } -//TODO: Not sure if this is even a valid property: The rule fails. +//TODO: Not sure if this is even a valid property.... rule redeemInOneTransactionIsPreferable(address user, address receiver, uint256 s1, uint256 s2) { env e; @@ -80,4 +82,4 @@ rule redeemInOneTransactionIsPreferable(address user, address receiver, uint256 assert(redeemed2 <= redeemed1a + redeemed1b); } -//Current results: https://prover.certora.com/output/53900/c3da8b5edf6f4561bf13bc12a8f73afc?anonymousKey=30965a0f354b3452eb87d8a35272c68121ce9015 \ No newline at end of file +//Current results: https://prover.certora.com/output/53900/d78afb175b134ae1b55af991011caff5?anonymousKey=810e6e3b5caa8b72aa3173c4937b565d36a6e38b \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec index 6bc385e..01b7c04 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec @@ -65,7 +65,7 @@ rule redeemMustDecreaseTotalAssets(uint256 shares, address receiver, address own //`decimals()` should be larger than or equal to `asset.decimals()` rule decimalsOfUnderlyingVaultShouldBeLarger(uint256 shares, address receiver, address owner){ - //TODO: Rule fails. The method call to decimals returns a HAVOC'd value. Still the solver should be able to reason that ERC4626.decimals == ERC20.decimals as of the call to the super constructor. Don't understand why. + //TODO: Rule fails. The method call to decimals returns a HAVOC'd value. Still the solver should be able to reason that ERC4626.decimals == ERC20.decimals as of the call to the super constructor. safeAssumptions(); uint8 assetDecimals = __ERC20.decimals(); @@ -74,4 +74,4 @@ rule decimalsOfUnderlyingVaultShouldBeLarger(uint256 shares, address receiver, a assert decimals >= assetDecimals, "Decimals of underlying ERC20 should be larger than ERC4626 decimals."; } -//Current results: https://prover.certora.com/output/53900/07ab0436f2424a539eb23cd32429c81c?anonymousKey=8ca00e5e8b0af8d70a7f6bb862e3ab7332c10d92 \ No newline at end of file +//Current results: https://prover.certora.com/output/53900/37a8a72cd1f6426383fa9d0d884dff39?anonymousKey=20d731f3d50ddececfa308670b0848ee616e4e58 \ No newline at end of file From fcc611be83c1765b38b35c2a5f3637919f499173 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 12 Sep 2023 16:38:41 +0200 Subject: [PATCH 005/125] Modular verification --- .../certoraRun-InflationAttack.conf | 10 +-- .../certoraRun-OZ-Modular.conf | 16 ++++ .../src/certora/specs/ERC20-OZ-Modular.spec | 79 ++++++++++++++++++ .../specs/ERC4626-InflationAttack.spec | 6 +- .../specs/ERC4626-MonotonicityInvariant.spec | 82 ++++++++++++------- .../contracts/token/ERC20/ERC20.sol | 5 ++ 6 files changed, 160 insertions(+), 38 deletions(-) create mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf create mode 100644 lesson4_reading/erc4626/src/certora/specs/ERC20-OZ-Modular.spec diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf index fcb20f7..0ef6e4b 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf @@ -6,14 +6,10 @@ "verify": "ERC4626Mock:src/certora/specs/ERC4626-InflationAttack.spec", "link": ["ERC4626Mock:_asset=ERC20Mock"], "solc": "solc8.20", - "prover_args": [ - "-numOfUnsatCores 1", - "-canonicalizeTAC false" - ], "prover_version": "master", - "server": "staging", - "send_only": true, + "server": "production", "rule_sanity": "basic", + "send_only": true, "msg": "Verification of ERC4626", - "optimistic_loop": true, + "optimistic_loop": true } \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf new file mode 100644 index 0000000..9288ec4 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf @@ -0,0 +1,16 @@ +{ + "files": [ + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + ], + "verify": "ERC20Mock:src/certora/specs/ERC20-OZ-Modular.spec", + "solc": "solc8.20", + "prover_args": [ + "-numOfUnsatCores 1", + "-canonicalizeTAC false" + ], + "prover_version": "master", + "server": "production", + "send_only": true, + "msg": "Modular verification of ERC 20 spec", + "optimistic_loop": true +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC20-OZ-Modular.spec b/lesson4_reading/erc4626/src/certora/specs/ERC20-OZ-Modular.spec new file mode 100644 index 0000000..148c8fa --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs/ERC20-OZ-Modular.spec @@ -0,0 +1,79 @@ + +methods { + function totalSupply() external returns uint256 envfree; + function balanceOf(address) external returns uint256 envfree; +} + + +function safeAssumptions() { + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; +} + +rule verifyAssumptionOnUpdate(){ + env e; + address from; + address to; + uint256 amount; + + safeAssumptions(); + + mathint balanceOfToBefore = balanceOf(to); + mathint balanceOfFromBefore = balanceOf(from); + mathint totalSupplyBefore = totalSupply(); + + wrapperUpdate(e, from, to, amount); + + + mathint balanceOfToAfter = balanceOf(to); + mathint balanceOfFromAfter = balanceOf(from); + mathint totalSupplyAfter = totalSupply(); + + assert to != 0 && from != to => balanceOfToAfter == balanceOfToBefore + amount; + assert from != 0 && from != to => balanceOfFromAfter == balanceOfFromBefore - amount; + assert from == to => balanceOfFromAfter == balanceOfFromBefore; + + assert to == 0 && from != 0 => totalSupplyAfter == totalSupplyBefore - amount; + assert from == 0 && to != 0 => totalSupplyAfter == totalSupplyBefore + amount; + assert to != 0 && from != 0 => totalSupplyAfter == totalSupplyBefore; +} + + +rule noneSense(){ + address x; + uint256 amount; + uint256 val = balanceOf(x); + + require balanceOf(x) + amount == val; + assert amount > 0; +} + + + +invariant sumOfBalancesEqualsTotalSupplyERC20() + sumOfBalancesERC20 == to_mathint(totalSupply()); + +ghost mathint sumOfBalancesERC20 { + init_state axiom sumOfBalancesERC20 == 0; +} + +hook Sstore _balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; + userBalanceERC20 = newValue; +} + +hook Sload uint256 value _balances[KEY address auser] STORAGE { + //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to already hold. + require to_mathint(value) <= sumOfBalancesERC20; +} + +invariant singleUserBalanceSmallerThanTotalSupplyERC20() + userBalanceERC20 <= sumOfBalancesERC20; + +ghost mathint userBalanceERC20 { + init_state axiom userBalanceERC20 == 0; +} + + +//Current results: https://prover.certora.com/output/53900/1c908f0eff9c42518fc6206e42152cd8/?anonymousKey=435376258db9ce72101337c61da0d6e95b45d02f \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec index 6388da2..eeec050 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec @@ -6,9 +6,10 @@ using ERC20Mock as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupply; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; methods{ function __ERC20.allowance(address,address) external returns uint256 envfree; @@ -64,7 +65,6 @@ rule vulnerableToInflationAttack(address attacker, address victim, address depos //require forall address x. balanceOf(x) <= totalSupply(); //Doesn't work //require forall address y. __ERC20.balanceOf(y) <= __ERC20.totalSupply(); - safeAssumptions(); uint256 oneEther; uint256 oneWei; diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec index cdbc598..e68a3e4 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec @@ -11,25 +11,42 @@ methods { function previewDeposit(uint256) external returns uint256 envfree; function previewRedeem(uint256) external returns uint256 envfree; function _ERC20.totalSupply() external returns uint256 envfree; - function ERC20._update(address from, address to, uint256 value) internal => updateStub(from, to, value); + //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); } -function updateStub(address from, address to ,uint256 amount) { - uint256 balanceOfTo = balanceOf(to); - uint256 balanceOfFrom = balanceOf(from); - uint256 totalSupply = totalSupply(); - //TODO: verify the assumptions by writing a rule for it. - if(to != from){ - require (balanceOf(to) == require_uint256(balanceOfTo + amount)); - require (balanceOf(from) == require_uint256(balanceOfFrom - amount)); - require (require_uint256(totalSupply + amount) == totalSupply()); - } -} function safeAssumptions(){ requireInvariant totalAssetsZeroImpliesTotalSupplyZero; - requireInvariant sumOfBalancesEqualsTotalSupply; + requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; +} + + +function updateSafe(address from, address to, uint256 amount){ + + uint256 balanceOfTo = balanceOf(to); + uint256 balanceOfFrom = balanceOf(from); + uint256 totalSupply = totalSupply(); + + + //transfer or mint case + if(to != 0 && from != to){ + require(balanceOfTo >= amount); + } + //transfer or burn case + if(from != 0 && from != to){ + require(balanceOfFrom >= amount); + } + //mint case + if(from == 0 && to != 0){ + require(totalSupply >= amount); + } + //burn case + if(from != 0 && to == 0){ + require(totalSupply >= amount); + } } rule assetAndShareMonotonicy(){ @@ -72,25 +89,27 @@ invariant totalAssetsZeroImpliesTotalSupplyZero() { preserved { - requireInvariant sumOfBalancesEqualsTotalSupply; + requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; - requireInvariant singleUserBalanceSmallerThanTotalSupply; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; } } -invariant sumOfBalancesEqualsTotalSupply() - sumOfBalances == to_mathint(totalSupply()); +invariant sumOfBalancesEqualsTotalSupplyERC4626() + sumOfBalancesERC4626 == to_mathint(totalSupply()); -ghost mathint sumOfBalances { - init_state axiom sumOfBalances == 0; +ghost mathint sumOfBalancesERC4626 { + init_state axiom sumOfBalancesERC4626 == 0; } -hook Sstore _balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { - sumOfBalances = sumOfBalances + newValue - oldValue; +hook Sstore ERC4626Mock._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalancesERC4626 = sumOfBalancesERC4626 + newValue - oldValue; + userBalanceERC4626 = newValue; } -hook Sload uint256 value _balances[KEY address auser] STORAGE { +hook Sload uint256 value ERC4626Mock._balances[KEY address auser] STORAGE { //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to hold. - require to_mathint(value) <= sumOfBalances; + require to_mathint(value) <= sumOfBalancesERC4626; } invariant sumOfBalancesEqualsTotalSupplyERC20() @@ -102,7 +121,7 @@ ghost mathint sumOfBalancesERC20 { hook Sstore _ERC20._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; - userBalance = newValue; + userBalanceERC20 = newValue; } hook Sload uint256 value _ERC20._balances[KEY address auser] STORAGE { @@ -110,11 +129,18 @@ hook Sload uint256 value _ERC20._balances[KEY address auser] STORAGE { require to_mathint(value) <= sumOfBalancesERC20; } -invariant singleUserBalanceSmallerThanTotalSupply() - userBalance <= sumOfBalancesERC20; +invariant singleUserBalanceSmallerThanTotalSupplyERC20() + userBalanceERC20 <= sumOfBalancesERC20; + +ghost mathint userBalanceERC20 { + init_state axiom userBalanceERC20 == 0; +} + +invariant singleUserBalanceSmallerThanTotalSupplyERC4626() + userBalanceERC4626 <= sumOfBalancesERC4626; -ghost mathint userBalance { - init_state axiom userBalance == 0; +ghost mathint userBalanceERC4626 { + init_state axiom userBalanceERC4626 == 0; } //Current results: https://prover.certora.com/output/53900/1c908f0eff9c42518fc6206e42152cd8/?anonymousKey=435376258db9ce72101337c61da0d6e95b45d02f \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol index 8eeb314..52d19fe 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol @@ -369,4 +369,9 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { } } } + + + function wrapperUpdate(address from, address to, uint256 value) external { + _update(from, to, value); + } } From cb0d39b7986abb453d99d2d26cfe0ab3c624270e Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 12 Sep 2023 17:14:41 +0200 Subject: [PATCH 006/125] Adding spec for singleUserBalanceSmallerThanTotalSupply for ERC4626, adding run script --- lesson4_reading/erc4626/doIt.sh | 5 +++ lesson4_reading/erc4626/results.out | 14 +++++++ .../ERC4626-FunctionalAccountingProps.spec | 7 ++-- .../specs/ERC4626-InflationAttack.spec | 8 ++-- .../specs/ERC4626-MonotonicityInvariant.spec | 42 ++++++++++++------- .../specs/ERC4626-MustNotRevertProps.spec | 8 ++-- .../ERC4626-RedeemUsingApprovalProps.spec | 2 - .../certora/specs/ERC4626-RoundingProps.spec | 7 ++-- .../certora/specs/ERC4626-SecurityProps.spec | 7 ++-- 9 files changed, 64 insertions(+), 36 deletions(-) create mode 100755 lesson4_reading/erc4626/doIt.sh create mode 100644 lesson4_reading/erc4626/results.out diff --git a/lesson4_reading/erc4626/doIt.sh b/lesson4_reading/erc4626/doIt.sh new file mode 100755 index 0000000..e256347 --- /dev/null +++ b/lesson4_reading/erc4626/doIt.sh @@ -0,0 +1,5 @@ +for file in ./src/certora/conf/*.conf +do + echo -e "Results for $file can be found at:" >> results.out + certoraRun "$file" | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p' >> results.out +done \ No newline at end of file diff --git a/lesson4_reading/erc4626/results.out b/lesson4_reading/erc4626/results.out new file mode 100644 index 0000000..9c7dabb --- /dev/null +++ b/lesson4_reading/erc4626/results.out @@ -0,0 +1,14 @@ +-e Results for ./src/certora/conf/certoraRun-FunctionalAccountingProps.conf can be found at: +https://prover.certora.com/output/53900/cda4693ea49b4ccf804a9c36a4b714cf?anonymousKey=e140a7ee9ddc089f02c4fce1c7ea7068a914da3e +-e Results for ./src/certora/conf/certoraRun-InflationAttack.conf can be found at: +https://prover.certora.com/output/53900/03465b8029a04eeca44d223b88e44c4d?anonymousKey=a1b940370c5c1dee359929b22d707d1daeb51a07 +-e Results for ./src/certora/conf/certoraRun-MonotonicityInvariant.conf can be found at: +https://prover.certora.com/output/53900/c76405d1767f4122ad0e0dd9c16543e7?anonymousKey=3ba63b18dd0b64fee2ea1dcc0772a95c357b85b6 +-e Results for ./src/certora/conf/certoraRun-MustNotRevertProps.conf can be found at: +https://prover.certora.com/output/53900/154925817dcf4a42b67bd2e4893eabd5?anonymousKey=182f5e64096bf9d3c1b78dc4c2e235df4b699727 +-e Results for ./src/certora/conf/certoraRun-RedeemUsingApprovalProps.conf can be found at: +https://prover.certora.com/output/53900/30d5ffa88c7c42e285124c5b99529f97?anonymousKey=a33a2ab8ea27848cb2a7ac5d34ac8ae6983d0b96 +-e Results for ./src/certora/conf/certoraRun-RoundingProps.conf can be found at: +https://prover.certora.com/output/53900/0286f159d4e34f2f944a368a439160ab?anonymousKey=a3425a67032f62b18e69e490e74896fc36f5d6a2 +-e Results for ./src/certora/conf/certoraRun-SecurityProps.conf can be found at: +https://prover.certora.com/output/53900/09688a10ef8b453bba659ee5d86f2f45?anonymousKey=7bbfb60130f84061fe4cbd68732bf128a6ae4fa6 diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec index 89bfdb0..4607e02 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec @@ -5,9 +5,10 @@ using ERC20 as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupply; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; methods { function __ERC20.balanceOf(address) external returns uint256 envfree; @@ -126,5 +127,3 @@ rule redeemProperties(uint256 shares, address receiver, address owner){ assert assets >= previewAssets; } - -//Current results: https://prover.certora.com/output/53900/7538cffeb6f5475580378e6b3f25cefd?anonymousKey=a51328c86e7ebd55e0a9bab660fb6dc241ef0cbd \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec index f3348e2..467b035 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec @@ -6,9 +6,11 @@ using ERC20 as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupply; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; + methods{ function __ERC20.allowance(address,address) external returns uint256 envfree; @@ -158,5 +160,3 @@ rule vulnerableToInflationAttack(address attacker, address victim, address depos assert assetsAttackerPreAttack >= assetsAttackerPostAttack, "The attacker gained assets."; } - -//Current results: https://prover.certora.com/output/53900/478fe72720584bc6af003f4f9bf6e4c9?anonymousKey=56d469faee96a177b669d70fe199a2e318e2c714 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec index 64f8309..5ece342 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec @@ -15,8 +15,10 @@ methods { function safeAssumptions(){ requireInvariant totalAssetsZeroImpliesTotalSupplyZero; - requireInvariant sumOfBalancesEqualsTotalSupply; + requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; } rule assetAndShareMonotonicy(){ @@ -57,25 +59,28 @@ invariant totalAssetsZeroImpliesTotalSupplyZero() { preserved { - requireInvariant sumOfBalancesEqualsTotalSupply; + requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; - requireInvariant singleUserBalanceSmallerThanTotalSupply; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; } } -invariant sumOfBalancesEqualsTotalSupply() - sumOfBalances == to_mathint(totalSupply()); +invariant sumOfBalancesEqualsTotalSupplyERC4626() + sumOfBalancesERC4626 == to_mathint(totalSupply()); -ghost mathint sumOfBalances { - init_state axiom sumOfBalances == 0; +ghost mathint sumOfBalancesERC4626 { + init_state axiom sumOfBalancesERC4626 == 0; } hook Sstore balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { - sumOfBalances = sumOfBalances + newValue - oldValue; + sumOfBalancesERC4626 = sumOfBalancesERC4626 + newValue - oldValue; + userBalanceERC4626 = newValue; } + hook Sload uint256 value balanceOf[KEY address auser] STORAGE { //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to hold. - require to_mathint(value) <= sumOfBalances; + require to_mathint(value) <= sumOfBalancesERC4626; } invariant sumOfBalancesEqualsTotalSupplyERC20() @@ -87,7 +92,7 @@ ghost mathint sumOfBalancesERC20 { hook Sstore _ERC20.balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; - userBalance = newValue; + userBalanceERC20 = newValue; } hook Sload uint256 value _ERC20.balanceOf[KEY address auser] STORAGE { @@ -95,11 +100,18 @@ hook Sload uint256 value _ERC20.balanceOf[KEY address auser] STORAGE { require to_mathint(value) <= sumOfBalancesERC20; } -invariant singleUserBalanceSmallerThanTotalSupply() - userBalance <= sumOfBalancesERC20; +invariant singleUserBalanceSmallerThanTotalSupplyERC20() + userBalanceERC20 <= sumOfBalancesERC20; + +ghost mathint userBalanceERC20 { + init_state axiom userBalanceERC20 == 0; +} + + +invariant singleUserBalanceSmallerThanTotalSupplyERC4626() + userBalanceERC4626 <= sumOfBalancesERC4626; -ghost mathint userBalance { - init_state axiom userBalance == 0; +ghost mathint userBalanceERC4626 { + init_state axiom userBalanceERC4626 == 0; } -//Current results: https://prover.certora.com/output/53900/be641a64bf1d4660abfd9cce3bcaefbc?anonymousKey=8cb85092656a9f2039cbf14cb47f6a2576b9c91f \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec index 7658476..973409c 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec @@ -6,9 +6,11 @@ using ERC20 as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupply; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; + methods{ @@ -85,5 +87,3 @@ function callMethodsWithParamenter(env e, method f) returns bool { //Should be unreachable. return true; } - -//Current Results: https://prover.certora.com/output/53900/91ecbf874cc948acae93970617b46246?anonymousKey=34cd5488963f665fc6cbfc19d831c1206d1cce19 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec index 7a9d53b..5a972a4 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec @@ -116,5 +116,3 @@ rule thirdPartyRedeemRevertCase(uint256 shares, address receiver, address owner) invariant balanceOfERC20EqualToTotalAsset() totalAssets() == _ERC20.balanceOf(currentContract); - -//Current results: https://prover.certora.com/output/53900/69d41bd6444149c1870a74cd96f8588b?anonymousKey=97da89d85f8dcdb2bef2f2cd5349b1e5342ada40 diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index d3421a1..8b4effc 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -4,9 +4,11 @@ import "./ERC4626-MonotonicityInvariant.spec"; using ERC20 as __ERC20; use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupply; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; + methods{ @@ -80,4 +82,3 @@ rule redeemInOneTransactionIsPreferable(address user, address receiver, uint256 assert(redeemed2 <= redeemed1a + redeemed1b); } -//Current results: https://prover.certora.com/output/53900/c3da8b5edf6f4561bf13bc12a8f73afc?anonymousKey=30965a0f354b3452eb87d8a35272c68121ce9015 \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec index 6bc385e..da9d6ac 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec @@ -4,9 +4,10 @@ import "./ERC4626-MonotonicityInvariant.spec"; using ERC20 as __ERC20; use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupply; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupply; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; methods { @@ -73,5 +74,3 @@ rule decimalsOfUnderlyingVaultShouldBeLarger(uint256 shares, address receiver, a assert decimals >= assetDecimals, "Decimals of underlying ERC20 should be larger than ERC4626 decimals."; } - -//Current results: https://prover.certora.com/output/53900/07ab0436f2424a539eb23cd32429c81c?anonymousKey=8ca00e5e8b0af8d70a7f6bb862e3ab7332c10d92 \ No newline at end of file From 8f3118ca11d2441824d5e4020a9a32364d50cd66 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Wed, 13 Sep 2023 11:25:19 +0200 Subject: [PATCH 007/125] Fixes spec after merge --- .../erc4626/src/certora/specs/ERC4626-InflationAttack.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec index 467b035..f985c52 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec @@ -2,7 +2,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20 as __ERC20; +using ERC20Mock as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; From 641f676a9f254807c7dcdb0c9913ddaffa7f3af2 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Wed, 13 Sep 2023 11:25:39 +0200 Subject: [PATCH 008/125] Introducing inflation bug --- .gitignore | 2 ++ .../contracts/token/ERC20/extensions/ERC4626.sol | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3c3c63f..688e55e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ # vim .*.swp .*.swo + +emv-* diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index adc4f66..8b0448d 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -227,14 +227,18 @@ abstract contract ERC4626 is ERC20, IERC4626 { * @dev Internal conversion function (from assets to shares) with support for rounding direction. */ function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); + uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero. + + return supply == 0 ? assets : assets.mulDiv(supply, totalAssets()); } /** * @dev Internal conversion function (from shares to assets) with support for rounding direction. */ function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { - return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero. + + return supply == 0 ? shares : shares.mulDiv(totalAssets(), supply); } /** From 4189a10e14e60bedc7e001fe14ff7c3fea2fe1f1 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Wed, 13 Sep 2023 11:26:47 +0200 Subject: [PATCH 009/125] Add info to bash script --- lesson4_reading/erc4626/doIt.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/doIt.sh b/lesson4_reading/erc4626/doIt.sh index e256347..a45e67f 100755 --- a/lesson4_reading/erc4626/doIt.sh +++ b/lesson4_reading/erc4626/doIt.sh @@ -1,5 +1,9 @@ for file in ./src/certora/conf/*.conf + +echo "Using git commit " >> results.out +git rev-parse HEAD >> results.out +git git status --short >> results.out do - echo -e "Results for $file can be found at:" >> results.out + echo "Results for $file can be found at:" >> results.out certoraRun "$file" | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p' >> results.out done \ No newline at end of file From c811e4c9a971ccf4d2901e633b8b122b44a280d0 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Wed, 13 Sep 2023 11:33:06 +0200 Subject: [PATCH 010/125] Reverting introduction of bug and removing invariant that doesn't hold. --- .../src/certora/specs/ERC4626-MonotonicityInvariant.spec | 1 - .../contracts/token/ERC20/extensions/ERC4626.sol | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec index 83e6252..5a12deb 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec @@ -16,7 +16,6 @@ methods { function safeAssumptions(){ - requireInvariant totalAssetsZeroImpliesTotalSupplyZero; requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index 8b0448d..adc4f66 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -227,18 +227,14 @@ abstract contract ERC4626 is ERC20, IERC4626 { * @dev Internal conversion function (from assets to shares) with support for rounding direction. */ function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero. - - return supply == 0 ? assets : assets.mulDiv(supply, totalAssets()); + return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); } /** * @dev Internal conversion function (from shares to assets) with support for rounding direction. */ function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero. - - return supply == 0 ? shares : shares.mulDiv(totalAssets(), supply); + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); } /** From 896ee63e767a6f6ef81b76ecc42dfe76e516985e Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Wed, 13 Sep 2023 13:38:20 +0200 Subject: [PATCH 011/125] Adding mirrorBalance and adding assumptions --- lesson4_reading/erc4626/doIt.sh | 13 +- lesson4_reading/erc4626/results.out | 313 +++++++++++++++++- ...conf => certoraRun-OZ-Modular.conf-ignore} | 0 .../certoraRun-RedeemUsingApprovalProps.conf | 14 + .../certoraRun-RoundingProps.conf | 14 + .../certoraRun-SecurityProps.conf | 14 + .../src/certora/specs/ERC20-OZ-Modular.spec | 51 +-- .../ERC4626-FunctionalAccountingProps.spec | 4 +- .../specs/ERC4626-MonotonicityInvariant.spec | 36 +- .../specs/ERC4626-MustNotRevertProps.spec | 9 +- .../ERC4626-RedeemUsingApprovalProps.spec | 2 +- .../certora/specs/ERC4626-RoundingProps.spec | 2 +- .../certora/specs/ERC4626-SecurityProps.spec | 2 +- 13 files changed, 393 insertions(+), 81 deletions(-) rename lesson4_reading/erc4626/src/certora/conf-openzeppelin/{certoraRun-OZ-Modular.conf => certoraRun-OZ-Modular.conf-ignore} (100%) create mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RedeemUsingApprovalProps.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf diff --git a/lesson4_reading/erc4626/doIt.sh b/lesson4_reading/erc4626/doIt.sh index a45e67f..5d3b8b1 100755 --- a/lesson4_reading/erc4626/doIt.sh +++ b/lesson4_reading/erc4626/doIt.sh @@ -1,9 +1,10 @@ -for file in ./src/certora/conf/*.conf - -echo "Using git commit " >> results.out +echo -e "\n\n\n########### New execution ##########\n\n\n" >> results.out +echo -e "\n\n\n########### Started on $(date) ##########\n\n\n" >> results.out git rev-parse HEAD >> results.out -git git status --short >> results.out +git status --short >> results.out +for file in ./src/certora/conf-openzeppelin/*.conf do - echo "Results for $file can be found at:" >> results.out - certoraRun "$file" | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p' >> results.out + echo -e "\n\n\n" >> results.out + echo -e "Results for $file can be found at:" >> results.out + certoraRun "$file" >> results.out done \ No newline at end of file diff --git a/lesson4_reading/erc4626/results.out b/lesson4_reading/erc4626/results.out index 9c7dabb..9505da1 100644 --- a/lesson4_reading/erc4626/results.out +++ b/lesson4_reading/erc4626/results.out @@ -1,14 +1,299 @@ --e Results for ./src/certora/conf/certoraRun-FunctionalAccountingProps.conf can be found at: -https://prover.certora.com/output/53900/cda4693ea49b4ccf804a9c36a4b714cf?anonymousKey=e140a7ee9ddc089f02c4fce1c7ea7068a914da3e --e Results for ./src/certora/conf/certoraRun-InflationAttack.conf can be found at: -https://prover.certora.com/output/53900/03465b8029a04eeca44d223b88e44c4d?anonymousKey=a1b940370c5c1dee359929b22d707d1daeb51a07 --e Results for ./src/certora/conf/certoraRun-MonotonicityInvariant.conf can be found at: -https://prover.certora.com/output/53900/c76405d1767f4122ad0e0dd9c16543e7?anonymousKey=3ba63b18dd0b64fee2ea1dcc0772a95c357b85b6 --e Results for ./src/certora/conf/certoraRun-MustNotRevertProps.conf can be found at: -https://prover.certora.com/output/53900/154925817dcf4a42b67bd2e4893eabd5?anonymousKey=182f5e64096bf9d3c1b78dc4c2e235df4b699727 --e Results for ./src/certora/conf/certoraRun-RedeemUsingApprovalProps.conf can be found at: -https://prover.certora.com/output/53900/30d5ffa88c7c42e285124c5b99529f97?anonymousKey=a33a2ab8ea27848cb2a7ac5d34ac8ae6983d0b96 --e Results for ./src/certora/conf/certoraRun-RoundingProps.conf can be found at: -https://prover.certora.com/output/53900/0286f159d4e34f2f944a368a439160ab?anonymousKey=a3425a67032f62b18e69e490e74896fc36f5d6a2 --e Results for ./src/certora/conf/certoraRun-SecurityProps.conf can be found at: -https://prover.certora.com/output/53900/09688a10ef8b453bba659ee5d86f2f45?anonymousKey=7bbfb60130f84061fe4cbd68732bf128a6ae4fa6 +-e + + +########### New execution ########## + + + +-e + + +########### Started on Wed Sep 13 11:52:43 CEST 2023 ########## + + + +c811e4c9a971ccf4d2901e633b8b122b44a280d0 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-FunctionalAccountingProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/589b802bed9447f99cece7c32e71a41a?anonymousKey=2262cba1db3ee5c1bf0b4173094f0e68397a569b +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/6b07f818e8cb45c684dc26a641db900a?anonymousKey=a5bee64bcf1b9a84dd8a2edcd37cd827c451abb0 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-MonotonicityInvariant.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/0a010c866c1e45a58c7e026bbc86fdcd?anonymousKey=754ccffdf7256cb161fe3020e4439c17aec40235 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-MustNotRevertProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/fa11af735c9e405b9da26f577be17a27?anonymousKey=88b2dbbe94f5f8d83f22c09b4307e43a4e4ac3dc +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +ERROR: Failed to compile spec file in /Users/johannesspath/Documents/Git/tutorials-code/lesson4_reading/erc4626/.certora_internal/23_09_13_11_53_11_441 +CRITICAL: Found errors +CRITICAL: [main] ERROR ALWAYS - Error in spec file (ERC4626-MonotonicityInvariant.spec:8:5): External method declaration for ERC20Mock.totalAssets() returns (uint256) does not correspond to any known declaration. Did you mean to add optional? +CRITICAL: [main] ERROR ALWAYS - Error in spec file (ERC4626-MonotonicityInvariant.spec:9:5): External method declaration for ERC20Mock.previewMint(uint256) returns (uint256) does not correspond to any known declaration. Did you mean to add optional? +CRITICAL: [main] ERROR ALWAYS - Error in spec file (ERC4626-MonotonicityInvariant.spec:10:5): External method declaration for ERC20Mock.previewWithdraw(uint256) returns (uint256) does not correspond to any known declaration. Did you mean to add optional? +CRITICAL: [main] ERROR ALWAYS - Error in spec file (ERC4626-MonotonicityInvariant.spec:11:5): External method declaration for ERC20Mock.previewDeposit(uint256) returns (uint256) does not correspond to any known declaration. Did you mean to add optional? +CRITICAL: [main] ERROR ALWAYS - Error in spec file (ERC4626-MonotonicityInvariant.spec:12:5): External method declaration for ERC20Mock.previewRedeem(uint256) returns (uint256) does not correspond to any known declaration. Did you mean to add optional? +CRITICAL: Encountered an error running Certora Prover: +CVL specification syntax and type check failed +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-RedeemUsingApprovalProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +CRITICAL: Encountered an error running Certora Prover: +/Users/johannesspath/Documents/Git/CVT-Executables-Mac/solc8.0 had an error: +ParserError: Source file requires different compiler version (current compiler is 0.8.0+commit.c7dfd78e.Darwin.appleclang) - note that nightly builds are considered to be strictly less than the released version + --> /Users/johannesspath/Documents/Git/tutorials-code/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol:2:1: + | +2 | pragma solidity ^0.8.20; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + + +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/29948ba728864a2b93b3d6812aa10d2d?anonymousKey=375a4cae866ffca3428f7ab4cf7470b4ed9c24b7 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/e9377ba043224b559c83d3f52d1114f5?anonymousKey=a548d58ad295e43982dc7fc3ecc70020db8b3e40 +-e + + +########### New execution ########## + + + +-e + + +########### Started on Wed Sep 13 12:00:23 CEST 2023 ########## + + + +c811e4c9a971ccf4d2901e633b8b122b44a280d0 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-FunctionalAccountingProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/f44d98f7305f435797b931b57396c735?anonymousKey=b3e9cebeb330d1e5959d36848201f73b19fb5c5c +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/745442ce1b9442aca9a15f0e3cde02e0?anonymousKey=817ecf59c8c7d23f9f111156957d9311beed67d5 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-MonotonicityInvariant.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/1e1631c22fea44e49dc7bd6cf1e4b5cf?anonymousKey=a21f4188243af37140fddc1c7dcb41ed3da3029d +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-MustNotRevertProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/5ef8c739d82a4442a4c03b17bcca8e90?anonymousKey=84b13caf75cbdab928207f495a29e1a7c9a9df03 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-RedeemUsingApprovalProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/8a1bb2ec704d4327abfb6f91f5e4fab2?anonymousKey=6341584b2bad4b2f78933381a1fdd813a4ff9456 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/d84eb38292ff457fb97fb695fe2d8156?anonymousKey=6f5a93f867b930e40e2cb2ee515fc7f8bec9aa44 +-e + + + +-e Results for ./src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf can be found at: +WARNING: Default package file package.json not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +WARNING: Default package file remappings.txt not found, external contract dependencies could be unresolved. Ignore if solc invocation was successful +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol to expose internal function information... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol... +Compiling src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol to expose internal function information... + +Connecting to server... + +Job submitted to server + +Follow your job at https://prover.certora.com +Once the job is completed, the results will be available at https://prover.certora.com/output/53900/3d3ae3662478429eacd8eb58a203271a?anonymousKey=a6b78821c940f3c410ef8eaeee865dd347510705 diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf-ignore similarity index 100% rename from lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf rename to lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf-ignore diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RedeemUsingApprovalProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RedeemUsingApprovalProps.conf new file mode 100644 index 0000000..67b99f6 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RedeemUsingApprovalProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" + ], + "verify": "ERC4626Mock:src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec", + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "solc": "solc8.20", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf new file mode 100644 index 0000000..0de63f2 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" + ], + "verify": "ERC4626Mock:src/certora/specs/ERC4626-RoundingProps.spec", + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "solc": "solc8.20", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf new file mode 100644 index 0000000..aabcf24 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf @@ -0,0 +1,14 @@ +{ + "files": [ + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" + ], + "verify": "ERC4626Mock:src/certora/specs/ERC4626-SecurityProps.spec", + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "solc": "solc8.20", + "server": "production", + "send_only": true, + "rule_sanity": "basic", + "msg": "Verification of ERC4626", + "optimistic_loop": true, +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC20-OZ-Modular.spec b/lesson4_reading/erc4626/src/certora/specs/ERC20-OZ-Modular.spec index 148c8fa..b081c8a 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC20-OZ-Modular.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC20-OZ-Modular.spec @@ -1,15 +1,13 @@ +import "./ERC4626-MonotonicityInvariant.spec"; + methods { function totalSupply() external returns uint256 envfree; function balanceOf(address) external returns uint256 envfree; } - -function safeAssumptions() { - requireInvariant sumOfBalancesEqualsTotalSupplyERC20; - requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; - requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; -} +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant sumOfBalancesEqualsTotalSupplyERC20; rule verifyAssumptionOnUpdate(){ env e; @@ -17,7 +15,7 @@ rule verifyAssumptionOnUpdate(){ address to; uint256 amount; - safeAssumptions(); + safeAssumptionsERC20(); mathint balanceOfToBefore = balanceOf(to); mathint balanceOfFromBefore = balanceOf(from); @@ -38,42 +36,3 @@ rule verifyAssumptionOnUpdate(){ assert from == 0 && to != 0 => totalSupplyAfter == totalSupplyBefore + amount; assert to != 0 && from != 0 => totalSupplyAfter == totalSupplyBefore; } - - -rule noneSense(){ - address x; - uint256 amount; - uint256 val = balanceOf(x); - - require balanceOf(x) + amount == val; - assert amount > 0; -} - - - -invariant sumOfBalancesEqualsTotalSupplyERC20() - sumOfBalancesERC20 == to_mathint(totalSupply()); - -ghost mathint sumOfBalancesERC20 { - init_state axiom sumOfBalancesERC20 == 0; -} - -hook Sstore _balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { - sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; - userBalanceERC20 = newValue; -} - -hook Sload uint256 value _balances[KEY address auser] STORAGE { - //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to already hold. - require to_mathint(value) <= sumOfBalancesERC20; -} - -invariant singleUserBalanceSmallerThanTotalSupplyERC20() - userBalanceERC20 <= sumOfBalancesERC20; - -ghost mathint userBalanceERC20 { - init_state axiom userBalanceERC20 == 0; -} - - -//Current results: https://prover.certora.com/output/53900/1c908f0eff9c42518fc6206e42152cd8/?anonymousKey=435376258db9ce72101337c61da0d6e95b45d02f \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec index 4607e02..ea13e96 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec @@ -1,7 +1,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to __ERC20 as of import that already declares _ERC20. -using ERC20 as __ERC20; +using ERC20Mock as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; @@ -9,6 +9,8 @@ use invariant sumOfBalancesEqualsTotalSupplyERC4626; use invariant sumOfBalancesEqualsTotalSupplyERC20; use invariant singleUserBalanceSmallerThanTotalSupplyERC20; use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; +use invariant mirrorIsCorrectERC20; +use invariant mirrorIsCorrectERC4626; methods { function __ERC20.balanceOf(address) external returns uint256 envfree; diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec index 5a12deb..052a490 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec @@ -11,18 +11,30 @@ methods { function previewDeposit(uint256) external returns uint256 envfree; function previewRedeem(uint256) external returns uint256 envfree; function _ERC20.totalSupply() external returns uint256 envfree; - //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); + function _ERC20.balanceOf(address) external returns uint256 envfree; + // function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); } function safeAssumptions(){ + address x; requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; + requireInvariant mirrorIsCorrectERC4626(x); + requireInvariant mirrorIsCorrectERC20(x); } +function safeAssumptionsERC20() { + address x; + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; + requireInvariant mirrorIsCorrectERC20(x); +} + +//TODO: Careful here: we have ERC462 and ERC20 balance and totalSupply... function updateSafe(address from, address to, uint256 amount){ uint256 balanceOfTo = balanceOf(to); @@ -105,10 +117,12 @@ ghost mathint sumOfBalancesERC4626 { hook Sstore ERC4626Mock._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { sumOfBalancesERC4626 = sumOfBalancesERC4626 + newValue - oldValue; userBalanceERC4626 = newValue; + balanceOfMirroredERC4626[user] = newValue; } -hook Sload uint256 value ERC4626Mock._balances[KEY address auser] STORAGE { +hook Sload uint256 value ERC4626Mock._balances[KEY address user] STORAGE { //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to hold. require to_mathint(value) <= sumOfBalancesERC4626; + require value == balanceOfMirroredERC4626[user]; } invariant sumOfBalancesEqualsTotalSupplyERC20() @@ -121,11 +135,13 @@ ghost mathint sumOfBalancesERC20 { hook Sstore _ERC20._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; userBalanceERC20 = newValue; + balanceOfMirroredERC20[user] = newValue; } -hook Sload uint256 value _ERC20._balances[KEY address auser] STORAGE { +hook Sload uint256 value _ERC20._balances[KEY address user] STORAGE { //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to already hold. require to_mathint(value) <= sumOfBalancesERC20; + require value == balanceOfMirroredERC20[user]; } invariant singleUserBalanceSmallerThanTotalSupplyERC20() @@ -142,3 +158,17 @@ ghost mathint userBalanceERC4626 { init_state axiom userBalanceERC4626 == 0; } +ghost mapping(address => uint256) balanceOfMirroredERC4626 { + init_state axiom forall address a. (balanceOfMirroredERC4626[a] == 0); +} + +ghost mapping(address => uint256) balanceOfMirroredERC20 { + init_state axiom forall address a. (balanceOfMirroredERC20[a] == 0); +} + +invariant mirrorIsCorrectERC20(address x) + balanceOfMirroredERC20[x] == _ERC20.balanceOf(x); + + +invariant mirrorIsCorrectERC4626(address x) + balanceOfMirroredERC4626[x] == balanceOf(x); \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec index 69ae121..cf051d9 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec @@ -3,7 +3,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20 as __ERC20; +using ERC20Mock as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; @@ -26,13 +26,6 @@ methods{ function totalSupply() external returns uint256 envfree; } -//ERC4626-Monotonicity declares safeAssumptions(), why is it required to activeley declare the invariants used in safeAssumptions in this file? -use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupply; -use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupply; - - definition nonReveritngFunction(method f) returns bool = f.selector == sig:convertToAssets(uint256).selector diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec index 5a972a4..fecdb3c 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec @@ -1,4 +1,4 @@ -using ERC20 as _ERC20; +using ERC20Mock as _ERC20; methods { function _ERC20.balanceOf(address) external returns uint256 envfree; diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 8b4effc..b6fea06 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -1,7 +1,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20 as __ERC20; +using ERC20Mock as __ERC20; use invariant totalAssetsZeroImpliesTotalSupplyZero; use invariant sumOfBalancesEqualsTotalSupplyERC4626; diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec index da9d6ac..6ea85a5 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec @@ -1,7 +1,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20 as __ERC20; +using ERC20Mock as __ERC20; use invariant totalAssetsZeroImpliesTotalSupplyZero; use invariant sumOfBalancesEqualsTotalSupplyERC4626; From 12b2a2fde7a703b45ae015f63942d986eee0697c Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Wed, 13 Sep 2023 13:48:15 +0200 Subject: [PATCH 012/125] Manually assuming totalSupply == sumOfBalances as invariant isn't working --- .../certora/specs/ERC4626-RoundingProps.spec | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 8b4effc..24628cf 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -13,14 +13,39 @@ use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; methods{ function balanceOf(address) external returns uint256 envfree; + function __ERC20.balanceOf(address) external returns uint256 envfree; function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; } +function assumeBalaneEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ + mathint totalSupply = totalSupply(); + mathint balanceOfAddr1 = balanceOf(addr1); + mathint balanceOfAddr2 = balanceOf(addr2); + mathint balanceOfAddr3 = balanceOf(addr3); + mathint balanceOfAddr4 = balanceOf(addr4); + + require totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3 + balanceOfAddr4; +} + +function assumeBalaneEqualSumManualERC20_4(address addr1,address addr2,address addr3, address addr4){ + mathint totalSupply = __ERC20.totalSupply(); + mathint balanceOfAddr1 = __ERC20.balanceOf(addr1); + mathint balanceOfAddr2 = __ERC20.balanceOf(addr2); + mathint balanceOfAddr3 = __ERC20.balanceOf(addr3); + mathint balanceOfAddr4 = __ERC20.balanceOf(addr4); + + require totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3 + balanceOfAddr4; +} + + + rule inverseDepositRedeemInFavourForVault(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_owner){ env e; safeAssumptions(); + assumeBalaneEqualSumManualERC4626_4(deposit_receiver, redeem_receiver, redeem_owner, e.msg.sender); + assumeBalaneEqualSumManualERC20_4(deposit_receiver, redeem_receiver, redeem_owner, e.msg.sender); uint256 shares = deposit(e, assets, deposit_receiver); uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); @@ -31,6 +56,9 @@ rule inverseDepositRedeemInFavourForVault(uint256 assets, address deposit_receiv rule inverseRedeemDepositInFavourForVault(uint256 shares, address deposit_receiver, address redeem_receiver, address redeem_owner){ env e; safeAssumptions(); + assumeBalaneEqualSumManualERC4626_4(deposit_receiver, redeem_receiver, redeem_owner, e.msg.sender); + assumeBalaneEqualSumManualERC20_4(deposit_receiver, redeem_receiver, redeem_owner, e.msg.sender); + uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); uint256 depositedShares = deposit(e, redeemedAssets, deposit_receiver); @@ -41,6 +69,8 @@ rule inverseRedeemDepositInFavourForVault(uint256 shares, address deposit_receiv rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; safeAssumptions(); + assumeBalaneEqualSumManualERC4626_4(mint_receiver, withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalaneEqualSumManualERC20_4(mint_receiver, withdraw_receiver, withdraw_owner, e.msg.sender); uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); @@ -51,6 +81,8 @@ rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, rule inverseWithdrawMintInFavourForVault(uint256 assets, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; safeAssumptions(); + assumeBalaneEqualSumManualERC4626_4(mint_receiver, withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalaneEqualSumManualERC20_4(mint_receiver, withdraw_receiver, withdraw_owner, e.msg.sender); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); uint256 mintedAssets = mint(e, withdrawnShares, mint_receiver); From 9fe3e87abff7f824d304d805d857567778093c52 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 09:46:29 +0200 Subject: [PATCH 013/125] Results for run: https://prover.certora.com/output/53900/231f5ee9474d4fa28224327ffbc8b505?anonymousKey=95c5677e62f99e7726f68500ff1820974c3ec27f --- lesson4_reading/erc4626/gitCertoraRun.sh | 6 + .../certoraRun-RoundingProps.conf | 6 +- .../certoraRun-SecurityProps.conf | 3 + .../specs/ERC4626-MonotonicityInvariant.spec | 21 ++- .../certora/specs/ERC4626-RoundingProps.spec | 138 ++++++++---------- .../contracts/mocks/token/DummyERC20.sol | 16 ++ .../contracts/token/ERC20/ERC20.sol | 42 ++++-- .../token/ERC20/extensions/ERC4626.sol | 11 +- .../contracts/utils/math/Math.sol | 4 +- 9 files changed, 144 insertions(+), 103 deletions(-) create mode 100755 lesson4_reading/erc4626/gitCertoraRun.sh create mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/DummyERC20.sol diff --git a/lesson4_reading/erc4626/gitCertoraRun.sh b/lesson4_reading/erc4626/gitCertoraRun.sh new file mode 100755 index 0000000..209c7a3 --- /dev/null +++ b/lesson4_reading/erc4626/gitCertoraRun.sh @@ -0,0 +1,6 @@ +certoraURL=$(certoraRun "$@" | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p') + +git add -A + +git commit -m "Results for run: $certoraURL" +echo $certoraURL \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index 0de63f2..f560a0f 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -4,8 +4,12 @@ "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], "verify": "ERC4626Mock:src/certora/specs/ERC4626-RoundingProps.spec", + "solc": "solc8.20", "link": ["ERC4626Mock:_asset=ERC20Mock"], - "solc": "solc8.20", + "prover_args": [ + "-smt_hashingScheme plainInjectivity" , + "-solvers [yices,z3]" + ], "server": "production", "send_only": true, "rule_sanity": "basic", diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf index aabcf24..ea0618c 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf @@ -6,6 +6,9 @@ "verify": "ERC4626Mock:src/certora/specs/ERC4626-SecurityProps.spec", "link": ["ERC4626Mock:_asset=ERC20Mock"], "solc": "solc8.20", + "prover_args": [ + "-smt_hashingScheme plainInjectivity -s [yices, z3]" + ], "server": "production", "send_only": true, "rule_sanity": "basic", diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec index 052a490..1427886 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec @@ -12,31 +12,38 @@ methods { function previewRedeem(uint256) external returns uint256 envfree; function _ERC20.totalSupply() external returns uint256 envfree; function _ERC20.balanceOf(address) external returns uint256 envfree; - // function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); + //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); + //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); } function safeAssumptions(){ - address x; requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; - requireInvariant mirrorIsCorrectERC4626(x); - requireInvariant mirrorIsCorrectERC20(x); } +function balaceMirrorsAreCorrect(address x) { + requireInvariant mirrorIsCorrectERC20(x); + requireInvariant mirrorIsCorrectERC4626(x); +} function safeAssumptionsERC20() { - address x; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; - requireInvariant mirrorIsCorrectERC20(x); +} + +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { + uint256 res; + require(res * denominator) <= x * y; + require(res * denominator) > x * y - 1; + return res; } //TODO: Careful here: we have ERC462 and ERC20 balance and totalSupply... function updateSafe(address from, address to, uint256 amount){ - uint256 balanceOfTo = balanceOf(to); uint256 balanceOfFrom = balanceOf(from); uint256 totalSupply = totalSupply(); diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 47a4503..139e419 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -1,116 +1,106 @@ -import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. using ERC20Mock as __ERC20; -use invariant totalAssetsZeroImpliesTotalSupplyZero; -use invariant sumOfBalancesEqualsTotalSupplyERC4626; -use invariant sumOfBalancesEqualsTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupplyERC20; -use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; - methods{ function balanceOf(address) external returns uint256 envfree; function __ERC20.balanceOf(address) external returns uint256 envfree; + function __ERC20.totalSupply() external returns uint256 envfree; function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; } -function assumeBalaneEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { + uint256 res; + require(res * denominator) <= x * y; + require((res + 1) * denominator) > x * y; + require x < denominator; + + require x <= denominator; + require res <= y; + + return res; +} +function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ mathint totalSupply = totalSupply(); mathint balanceOfAddr1 = balanceOf(addr1); mathint balanceOfAddr2 = balanceOf(addr2); mathint balanceOfAddr3 = balanceOf(addr3); mathint balanceOfAddr4 = balanceOf(addr4); - require totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3 + balanceOfAddr4; + //Case all different + require addr1 != addr2 && addr1 != addr3 && addr1 != addr4 && addr2 != addr3 && addr2 != addr4 && addr3 != addr4 => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3 + balanceOfAddr4; + + //Case two are equal + require (addr1 == addr2 && addr1 != addr3 && addr1 != addr4 && addr3 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr3 + balanceOfAddr4; + require (addr1 == addr3 && addr1 != addr2 && addr1 != addr4 && addr2 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; + require (addr1 == addr4 && addr1 != addr2 && addr1 != addr3 && addr2 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + require (addr2 == addr3 && addr2 != addr1 && addr2 != addr4 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; + require (addr2 == addr4 && addr2 != addr1 && addr2 != addr3 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + require (addr3 == addr4 && addr3 != addr1 && addr3 != addr2 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + + //Cases three are same + require (addr1 == addr2 && addr2 == addr3 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr4; //4 differs + require (addr1 == addr2 && addr2 == addr4 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr3; //3 differs + require (addr1 == addr3 && addr3 == addr4 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2; //2 differs + require (addr2 == addr3 && addr3 == addr4 && addr1 != addr2) => totalSupply == balanceOfAddr2 + balanceOfAddr1; //1 differs + + require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; } -function assumeBalaneEqualSumManualERC20_4(address addr1,address addr2,address addr3, address addr4){ +function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address addr3, address addr4){ mathint totalSupply = __ERC20.totalSupply(); mathint balanceOfAddr1 = __ERC20.balanceOf(addr1); mathint balanceOfAddr2 = __ERC20.balanceOf(addr2); mathint balanceOfAddr3 = __ERC20.balanceOf(addr3); mathint balanceOfAddr4 = __ERC20.balanceOf(addr4); - require totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3 + balanceOfAddr4; -} - - - -rule inverseDepositRedeemInFavourForVault(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_owner){ - env e; - safeAssumptions(); - assumeBalaneEqualSumManualERC4626_4(deposit_receiver, redeem_receiver, redeem_owner, e.msg.sender); - assumeBalaneEqualSumManualERC20_4(deposit_receiver, redeem_receiver, redeem_owner, e.msg.sender); + //Case all different + require addr1 != addr2 && addr1 != addr3 && addr1 != addr4 && addr2 != addr3 && addr2 != addr4 && addr3 != addr4 => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3 + balanceOfAddr4; - uint256 shares = deposit(e, assets, deposit_receiver); - uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); + //Case two are equal + require (addr1 == addr2 && addr1 != addr3 && addr1 != addr4 && addr3 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr3 + balanceOfAddr4; + require (addr1 == addr3 && addr1 != addr2 && addr1 != addr4 && addr2 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; + require (addr1 == addr4 && addr1 != addr2 && addr1 != addr3 && addr2 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + require (addr2 == addr3 && addr2 != addr1 && addr2 != addr4 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; + require (addr2 == addr4 && addr2 != addr1 && addr2 != addr3 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + require (addr3 == addr4 && addr3 != addr1 && addr3 != addr2 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; - assert assets >= redeemedAssets, "User cannot gain assets using deposit / redeem combination."; -} - -rule inverseRedeemDepositInFavourForVault(uint256 shares, address deposit_receiver, address redeem_receiver, address redeem_owner){ - env e; - safeAssumptions(); - assumeBalaneEqualSumManualERC4626_4(deposit_receiver, redeem_receiver, redeem_owner, e.msg.sender); - assumeBalaneEqualSumManualERC20_4(deposit_receiver, redeem_receiver, redeem_owner, e.msg.sender); - - - uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); - uint256 depositedShares = deposit(e, redeemedAssets, deposit_receiver); + //Cases three are same + require (addr1 == addr2 && addr2 == addr3 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr4; //4 differs + require (addr1 == addr2 && addr2 == addr4 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr3; //3 differs + require (addr1 == addr3 && addr3 == addr4 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2; //2 differs + require (addr2 == addr3 && addr3 == addr4 && addr1 != addr2) => totalSupply == balanceOfAddr2 + balanceOfAddr1; //1 differs - assert shares >= depositedShares, "User cannot gain shares using deposit / redeem combination."; + require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; } - -rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ - env e; - safeAssumptions(); - assumeBalaneEqualSumManualERC4626_4(mint_receiver, withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalaneEqualSumManualERC20_4(mint_receiver, withdraw_receiver, withdraw_owner, e.msg.sender); - - uint256 assets = mint(e, shares, mint_receiver); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; -} - -rule inverseWithdrawMintInFavourForVault(uint256 assets, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; - safeAssumptions(); - assumeBalaneEqualSumManualERC4626_4(mint_receiver, withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalaneEqualSumManualERC20_4(mint_receiver, withdraw_receiver, withdraw_owner, e.msg.sender); - - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - uint256 mintedAssets = mint(e, withdrawnShares, mint_receiver); - - assert assets >= mintedAssets, "User cannot gain assets using deposit / redeem combination."; -} + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); -//TODO: Not sure if this is even a valid property: The rule fails. -rule redeemInOneTransactionIsPreferable(address user, address receiver, uint256 s1, uint256 s2) { - env e; + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); - safeAssumptions(); - uint256 shares = require_uint256(s1 + s2); - //The below requires have been added to find more intuitive counter examples. - require(e.msg.sender != currentContract); - require(e.msg.sender != user); - require(e.msg.sender != receiver); - require(user != receiver); - require(totalAssets() >= totalSupply()); + require(e.msg.sender == withdraw_owner); - storage init = lastStorage; + require(totalAssets() == 100); + require(totalSupply() == 1000); - mathint redeemed1a = redeem(e, s1, receiver, user); - mathint redeemed1b = redeem(e, s2, receiver, user); - mathint redeemed2 = redeem(e, shares, receiver, user) at init; - assert(redeemed2 <= redeemed1a + redeemed1b); + uint256 assets = mint(e, shares, mint_receiver); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } - diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/DummyERC20.sol new file mode 100644 index 0000000..bc1376a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/DummyERC20.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract DummyERC20 is ERC20 { + constructor() ERC20("ERC20Mock2", "E20M2") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol index 52d19fe..18b61d0 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol @@ -162,7 +162,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { */ function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { address spender = _msgSender(); - _spendAllowance(from, spender, value); + // _spendAllowance(from, spender, value); _transfer(from, to, value); return true; } @@ -226,13 +226,10 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _transfer(address from, address to, uint256 value) internal { - if (from == address(0)) { - revert ERC20InvalidSender(address(0)); - } - if (to == address(0)) { - revert ERC20InvalidReceiver(address(0)); - } - _update(from, to, value); + //_update(from, to, value); + uint256 fromBalance = _balances[from]; + _balances[from] = fromBalance - value; + _balances[to] += value; } /** @@ -280,10 +277,15 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _mint(address account, uint256 value) internal { - if (account == address(0)) { - revert ERC20InvalidReceiver(address(0)); + + + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; + + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[account] += value; } - _update(address(0), account, value); } /** @@ -295,10 +297,22 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead */ function _burn(address account, uint256 value) internal { - if (account == address(0)) { - revert ERC20InvalidSender(address(0)); + + uint256 fromBalance = _balances[account]; + if (fromBalance < value) { + revert ERC20InsufficientBalance(address(0), fromBalance, value); } - _update(account, address(0), value); + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[account] = fromBalance - value; + } + + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; + } + + } /** diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index adc4f66..990a97f 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -227,14 +227,14 @@ abstract contract ERC4626 is ERC20, IERC4626 { * @dev Internal conversion function (from assets to shares) with support for rounding direction. */ function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); + return assets.mulDiv(totalSupply() + 1, totalAssets() + 1, rounding); } /** * @dev Internal conversion function (from shares to assets) with support for rounding direction. */ function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { - return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + return shares.mulDiv(totalAssets() + 1, totalSupply() + 1, rounding); } /** @@ -248,7 +248,8 @@ abstract contract ERC4626 is ERC20, IERC4626 { // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the // assets are transferred and before the shares are minted, which is a valid state. // slither-disable-next-line reentrancy-no-eth - SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _asset.transferFrom(caller, address(this), assets); + _mint(receiver, shares); emit Deposit(caller, receiver, assets, shares); @@ -265,7 +266,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { uint256 shares ) internal virtual { if (caller != owner) { - _spendAllowance(owner, caller, shares); + // _spendAllowance(owner, caller, shares); } // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the @@ -275,7 +276,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the // shares are burned and after the assets are transferred, which is a valid state. _burn(owner, shares); - SafeERC20.safeTransfer(_asset, receiver, assets); + _asset.transfer(receiver, assets); emit Withdraw(caller, receiver, owner, assets, shares); } diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol index 17ce4c8..ab4c77a 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol @@ -205,9 +205,9 @@ library Math { */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); - if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + /* if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { result += 1; - } + }*/ return result; } From 4aa2c9af317074816d07d3bd31113533ab344842 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 09:49:53 +0200 Subject: [PATCH 014/125] Results for run: https://prover.certora.com/output/53900/9939b71a19ac45d88a758e6b2073621b?anonymousKey=d8a394e440fdc6f93127a783f9cec986387125c5 --- lesson4_reading/erc4626/gitCertoraRun.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/gitCertoraRun.sh b/lesson4_reading/erc4626/gitCertoraRun.sh index 209c7a3..7bab42f 100755 --- a/lesson4_reading/erc4626/gitCertoraRun.sh +++ b/lesson4_reading/erc4626/gitCertoraRun.sh @@ -1,4 +1,7 @@ -certoraURL=$(certoraRun "$@" | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p') +certoraOutput=$(certoraRun "$@") + +echo $certoraOutput +certoraURL=$(echo $certoraOutput | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p') git add -A From 3600d56241304fb1f6a3f5cbafcbe1d9cdfda4e6 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 09:53:48 +0200 Subject: [PATCH 015/125] Results for run: https://prover.certora.com/output/53900/2d8c976e5a934f739a06b44959688e89?anonymousKey=b03ca6d753b295589a4b7a5bdc1433cfbd6b3b7e --- lesson4_reading/erc4626/gitCertoraRun.sh | 2 +- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lesson4_reading/erc4626/gitCertoraRun.sh b/lesson4_reading/erc4626/gitCertoraRun.sh index 7bab42f..c947591 100755 --- a/lesson4_reading/erc4626/gitCertoraRun.sh +++ b/lesson4_reading/erc4626/gitCertoraRun.sh @@ -1,6 +1,6 @@ certoraOutput=$(certoraRun "$@") -echo $certoraOutput +echo "$certoraOutput" certoraURL=$(echo $certoraOutput | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p') git add -A diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 139e419..ba556fe 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -95,10 +95,6 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(e.msg.sender == withdraw_owner); - require(totalAssets() == 100); - require(totalSupply() == 1000); - - uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From 8d411f6f4a4ec75adb48dd0193d7f0bab145371d Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 09:56:45 +0200 Subject: [PATCH 016/125] Results for run: https://prover.certora.com/output/53900/0f88a3b2e5cf4b8e86572fc27bd5156e?anonymousKey=88ee9a0ad9ad1f4a19d0b196fb75fdced368f9e4 --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index ba556fe..304d778 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -15,9 +15,8 @@ methods{ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; - require x < denominator; + // require(res * denominator) <= x * y; + // require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; From 3406ee012644ea89febcac25504a8121f96b9cf2 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 09:56:56 +0200 Subject: [PATCH 017/125] Results for run: https://prover.certora.com/output/53900/920ebe0fb8224930b6105516aa545bfe?anonymousKey=eb6dfa374115f4ed3ead5b5a8e83c0a7eede673c --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 -- 1 file changed, 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 304d778..1bdd4f8 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -92,8 +92,6 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(withdraw_receiver != 0); - require(e.msg.sender == withdraw_owner); - uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From 92f2e59daa5f8f2249375ba8ed6f5be384b05bff Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:01:39 +0200 Subject: [PATCH 018/125] Results for run: https://prover.certora.com/output/53900/bbf1d8a87d60429fa8ba3b90c347060e?anonymousKey=e677b975d9fb2ff24492c134b43d55c1eaedf8c5 --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 1bdd4f8..b79ba79 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -11,12 +11,13 @@ methods{ function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - // require(res * denominator) <= x * y; - // require((res + 1) * denominator) > x * y; + require(res * denominator) <= x * y; + require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; From 76d3fc4f2bf360ae7c7105ba590c1e3c2b70e0bc Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:01:55 +0200 Subject: [PATCH 019/125] Results for run: https://prover.certora.com/output/53900/c1ddb3e23c32423c8241de5ff80b24e9?anonymousKey=eb97e6af39dd0bdd18398136b4e55fcbb0dd92a4 --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index b79ba79..2797776 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -16,8 +16,8 @@ methods{ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; + //require(res * denominator) <= x * y; + //require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; From 31efeb7e66df2a4c10959eb6bea2ae851a057bec Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:36:47 +0200 Subject: [PATCH 020/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- lesson4_reading/erc4626/gitCertoraRun.sh | 2 +- .../certora/specs/ERC4626-RoundingProps.spec | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lesson4_reading/erc4626/gitCertoraRun.sh b/lesson4_reading/erc4626/gitCertoraRun.sh index c947591..09652b5 100755 --- a/lesson4_reading/erc4626/gitCertoraRun.sh +++ b/lesson4_reading/erc4626/gitCertoraRun.sh @@ -5,5 +5,5 @@ certoraURL=$(echo $certoraOutput | sed -n 's/.*\(https:\/\/prover.certora.com\/o git add -A -git commit -m "Results for run: $certoraURL" +git commit -m "Results for run: $certoraURL with arguments $@" echo $certoraURL \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 2797776..4c58019 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -16,8 +16,8 @@ methods{ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - //require(res * denominator) <= x * y; - //require((res + 1) * denominator) > x * y; + require(res * denominator) <= x * y; + require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; @@ -41,7 +41,12 @@ function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,addres require (addr2 == addr3 && addr2 != addr1 && addr2 != addr4 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; require (addr2 == addr4 && addr2 != addr1 && addr2 != addr3 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; require (addr3 == addr4 && addr3 != addr1 && addr3 != addr2 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; - + + //Cases two are equal and the other two as well. + require (addr1 == addr2 && addr3 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr3; + require (addr1 == addr3 && addr2 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2; + require (addr2 == addr3 && addr1 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2; + //Cases three are same require (addr1 == addr2 && addr2 == addr3 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr4; //4 differs require (addr1 == addr2 && addr2 == addr4 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr3; //3 differs @@ -68,6 +73,11 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address require (addr2 == addr3 && addr2 != addr1 && addr2 != addr4 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; require (addr2 == addr4 && addr2 != addr1 && addr2 != addr3 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; require (addr3 == addr4 && addr3 != addr1 && addr3 != addr2 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + + //Cases two are equal and the other two as well. + require (addr1 == addr2 && addr3 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr3; + require (addr1 == addr3 && addr2 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2; + require (addr2 == addr3 && addr1 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2; //Cases three are same require (addr1 == addr2 && addr2 == addr3 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr4; //4 differs @@ -98,3 +108,9 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } + + +rule convertToAssetsMonotone(){ + uint256 x; + uint256 y; +} \ No newline at end of file From bef89b011b4ec2b2fe7e8ff1a1d7e89b38ba5cdf Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:37:23 +0200 Subject: [PATCH 021/125] Results for run: https://prover.certora.com/output/53900/75f5f4f3d28a4382a1e75ffcaa6f23b8?anonymousKey=eaba7fbede3b755b6e06ccca83805caaddfb8e6f with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 4c58019..c5a1b80 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -108,9 +108,3 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } - - -rule convertToAssetsMonotone(){ - uint256 x; - uint256 y; -} \ No newline at end of file From 0e9acfdb5f6577bc5dfbb76f896c1cea5822fddf Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:44:39 +0200 Subject: [PATCH 022/125] Results for run: https://prover.certora.com/output/53900/6a1e1b49aaf247af92ddfa2d475ff848?anonymousKey=434e0e7b5de57acf2a7535e7b89ad497b97ef7d1 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 3 --- 1 file changed, 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index c5a1b80..ba847f8 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -93,9 +93,6 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - //Dismiss allowance case - require(e.msg.sender == withdraw_owner); - //Make all non zero to avoid unnecessary cases. require(e.msg.sender != 0); require(mint_receiver != 0); From 31c815b1217295a4751175cd2348a20b01f9a09f Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:45:13 +0200 Subject: [PATCH 023/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index ba847f8..266873d 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -93,12 +93,18 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); + //Make all non zero to avoid unnecessary cases. require(e.msg.sender != 0); require(mint_receiver != 0); require(withdraw_owner != 0); require(withdraw_receiver != 0); + require(e.msg.sender != currentContract); + require(e.msg.sender != ERC20); + uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From 7c13af27b93a7ffda687c6f5afd44efb32abfef0 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:45:31 +0200 Subject: [PATCH 024/125] Results for run: https://prover.certora.com/output/53900/4cfa7a4bf0384f88ad532931801f0535?anonymousKey=3668bd548cd9a7a132e1279f31076b14cc3a2ded with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 266873d..c9f9d48 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -103,7 +103,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(withdraw_receiver != 0); require(e.msg.sender != currentContract); - require(e.msg.sender != ERC20); + require(e.msg.sender != __ERC20); uint256 assets = mint(e, shares, mint_receiver); From 668670c0f02098e6984e1b3ac67b828eca12a0cd Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:50:57 +0200 Subject: [PATCH 025/125] Results for run: https://prover.certora.com/output/53900/41a4bc5b38ad4196b5440faf3da7e93c?anonymousKey=157ffed5e74f6268ac0b7885c76bd6e212dffd47 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index c9f9d48..e004a1e 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -19,8 +19,8 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 require(res * denominator) <= x * y; require((res + 1) * denominator) > x * y; - require x <= denominator; - require res <= y; + // require x <= denominator; + // require res <= y; return res; } From 74b75d39f72b44b38b3167260284ef06730fa19e Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:51:55 +0200 Subject: [PATCH 026/125] Results for run: https://prover.certora.com/output/53900/552f67065c1c47e39db2c2116fbd2cab?anonymousKey=48f49e511bd937ecc77323388937c501971267d6 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index e004a1e..0422872 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -16,11 +16,11 @@ methods{ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; + // require(res * denominator) <= x * y; + // require((res + 1) * denominator) > x * y; - // require x <= denominator; - // require res <= y; + require x <= denominator; + require res <= y; return res; } From 0502a59ef9b2b19e94a06c922e1b82ee3df78ea9 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:59:43 +0200 Subject: [PATCH 027/125] Results for run: https://prover.certora.com/output/53900/e8f2165008cc4a609bc9c957048b7a63?anonymousKey=4f05de0fb2412ff4376b913e8011d0fad264b633 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 0422872..55935ff 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -17,7 +17,7 @@ methods{ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; // require(res * denominator) <= x * y; - // require((res + 1) * denominator) > x * y; + require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; From 502e74a7063ddd81a5263c3438fae20fb29399e8 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 10:59:55 +0200 Subject: [PATCH 028/125] Results for run: https://prover.certora.com/output/53900/396218296fa1406092616a327c3eb344?anonymousKey=f040208a3b062fb93dabf8a9b59cd55ec83df1c7 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 55935ff..ce086c7 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -17,7 +17,7 @@ methods{ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; // require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; + require((res + 1) * denominator) >= x * y; require x <= denominator; require res <= y; From 83dc77d6c0317f56b4a3a77bcc02c060d55d710e Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:11:25 +0200 Subject: [PATCH 029/125] Reverting change --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index ce086c7..55935ff 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -17,7 +17,7 @@ methods{ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; // require(res * denominator) <= x * y; - require((res + 1) * denominator) >= x * y; + require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; From 51b813b5d3543c9904498ad723499e767399e98e Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:11:37 +0200 Subject: [PATCH 030/125] Results for run: https://prover.certora.com/output/53900/541dadad753d4cac9f84cf26cbd6c54d?anonymousKey=a76fbc45c939e8a20e09ba3340ba7c4d2cb25db2 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../contracts/token/ERC20/ERC20.sol | 47 ++++++------------- .../token/ERC20/extensions/ERC4626.sol | 11 ++--- .../contracts/utils/math/Math.sol | 4 +- 3 files changed, 21 insertions(+), 41 deletions(-) diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol index 18b61d0..8eeb314 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol @@ -162,7 +162,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { */ function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { address spender = _msgSender(); - // _spendAllowance(from, spender, value); + _spendAllowance(from, spender, value); _transfer(from, to, value); return true; } @@ -226,10 +226,13 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _transfer(address from, address to, uint256 value) internal { - //_update(from, to, value); - uint256 fromBalance = _balances[from]; - _balances[from] = fromBalance - value; - _balances[to] += value; + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(from, to, value); } /** @@ -277,15 +280,10 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _mint(address account, uint256 value) internal { - - - // Overflow check required: The rest of the code assumes that totalSupply never overflows - _totalSupply += value; - - unchecked { - // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. - _balances[account] += value; + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); } + _update(address(0), account, value); } /** @@ -297,22 +295,10 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * NOTE: This function is not virtual, {_update} should be overridden instead */ function _burn(address account, uint256 value) internal { - - uint256 fromBalance = _balances[account]; - if (fromBalance < value) { - revert ERC20InsufficientBalance(address(0), fromBalance, value); - } - unchecked { - // Overflow not possible: value <= fromBalance <= totalSupply. - _balances[account] = fromBalance - value; + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); } - - unchecked { - // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. - _totalSupply -= value; - } - - + _update(account, address(0), value); } /** @@ -383,9 +369,4 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { } } } - - - function wrapperUpdate(address from, address to, uint256 value) external { - _update(from, to, value); - } } diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index 990a97f..adc4f66 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -227,14 +227,14 @@ abstract contract ERC4626 is ERC20, IERC4626 { * @dev Internal conversion function (from assets to shares) with support for rounding direction. */ function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - return assets.mulDiv(totalSupply() + 1, totalAssets() + 1, rounding); + return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); } /** * @dev Internal conversion function (from shares to assets) with support for rounding direction. */ function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { - return shares.mulDiv(totalAssets() + 1, totalSupply() + 1, rounding); + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); } /** @@ -248,8 +248,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the // assets are transferred and before the shares are minted, which is a valid state. // slither-disable-next-line reentrancy-no-eth - _asset.transferFrom(caller, address(this), assets); - + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); _mint(receiver, shares); emit Deposit(caller, receiver, assets, shares); @@ -266,7 +265,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { uint256 shares ) internal virtual { if (caller != owner) { - // _spendAllowance(owner, caller, shares); + _spendAllowance(owner, caller, shares); } // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the @@ -276,7 +275,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the // shares are burned and after the assets are transferred, which is a valid state. _burn(owner, shares); - _asset.transfer(receiver, assets); + SafeERC20.safeTransfer(_asset, receiver, assets); emit Withdraw(caller, receiver, owner, assets, shares); } diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol index ab4c77a..17ce4c8 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol @@ -205,9 +205,9 @@ library Math { */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); - /* if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { result += 1; - }*/ + } return result; } From 115d3d0485d59a1dc366df86258509e601390b26 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:27:36 +0200 Subject: [PATCH 031/125] Results for run: https://prover.certora.com/output/53900/0409ffb487b040f0a67709fdefc342cc?anonymousKey=b2dd73754f5bf0314a74c721d9361f7af5dcc152 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 55935ff..84ef38d 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -21,7 +21,7 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 require x <= denominator; require res <= y; - + require x == 0 => res == 0; return res; } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ From c9ce91b710cb78aef4c8466862b5eeb37d707d75 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:28:33 +0200 Subject: [PATCH 032/125] Results for run: https://prover.certora.com/output/53900/9a7597d885a94e80973a083dbeccec25?anonymousKey=f6ff7e0eb011285036055331b9a54bf1bd12728d with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 84ef38d..8476027 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -22,6 +22,7 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 require x <= denominator; require res <= y; require x == 0 => res == 0; + require denominator > 0; return res; } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ From 4001e9e9f480e99c3fa51d64e3129953cc5c5cb2 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:40:52 +0200 Subject: [PATCH 033/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../certora/specs/ERC4626-RoundingProps.spec | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 8476027..0ae3fea 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -11,7 +11,9 @@ methods{ function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; - function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); + function convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); + function convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { @@ -25,6 +27,20 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 require denominator > 0; return res; } + +function convertToSharesSummary(uint256 assets) returns uint256 { + uint256 shares = convertToShares(assets); + require assets >= convertToAssets(shares); + return shares; +} + + +function convertToAssetsSummary(uint256 shares) returns uint256 { + uint256 assets = convertToAssets(shares); + require shares >= convertToShares(assets); + return assets; +} + function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ mathint totalSupply = totalSupply(); mathint balanceOfAddr1 = balanceOf(addr1); @@ -112,3 +128,6 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } + + +rule convertToAssetsConvertToShares() \ No newline at end of file From 75f68e4f3e4d8211b2cd782977e762bfd408d259 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:41:06 +0200 Subject: [PATCH 034/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 3 --- 1 file changed, 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 0ae3fea..3c46de6 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -128,6 +128,3 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } - - -rule convertToAssetsConvertToShares() \ No newline at end of file From ec2caa32ce7d66ad711de5ae3e8051f99f2890f6 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:41:33 +0200 Subject: [PATCH 035/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 3c46de6..1bf618e 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -11,6 +11,8 @@ methods{ function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; + function convertToShares(uint256 assets) external returns uint256 envfree; + function convertToShares(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); function convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); function convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); From cd98f1fe6c5aa05faed85fb320b0ea296d57fdbc Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:42:26 +0200 Subject: [PATCH 036/125] Results for run: https://prover.certora.com/output/53900/ac1117d3a90b45e590ca583695c6561f?anonymousKey=c0bca5e75a270a2f39f1bc002118852e3b40a83a with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 1bf618e..e5a1636 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -12,7 +12,7 @@ methods{ function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; function convertToShares(uint256 assets) external returns uint256 envfree; - function convertToShares(uint256 shares) external returns uint256 envfree; + function convertToAssets(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); function convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); function convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); From 86cdc1b5a927de73e9045887ea14eb3f929025a2 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:54:34 +0200 Subject: [PATCH 037/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../certora/specs/ERC4626-RoundingProps.spec | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index e5a1636..298c21d 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -17,7 +17,6 @@ methods{ function convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); function convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); } - function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; // require(res * denominator) <= x * y; @@ -30,14 +29,43 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 return res; } + +//The ghost is supposed to cover the equation: assets >= converToAssets(convertToShares(assets)). +ghost uint256 lastCallConvertToShares_AssetsParameter{ + init_state axiom lastCallConvertToShares_AssetsParameter == 0; +} +//The ghost is supposed to cover the equation: shares >= convertToShares(convertToAssets(shares)) +ghost uint256 lastCallConvertToAssets_SharesParameter{ + init_state axiom lastCallConvertToAssets_SharesParameter == 0; +} + + +function convertToSharesSummary(uint256 assets) returns uint256 { + lastCallConvertToShares_AssetsParameter = assets; + uint256 convertedShares = convertToShares(assets); + require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); + return convertedShares; +} + +function convertToAssetsSummary(uint256 shares) returns uint256 { + lastCallToConvertToAssets_SharesParameter = shares; + uint256 convertedAssets = convertToAssets(shares); + + require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedShares); + return convertedAssets; +} + + + function convertToSharesSummary(uint256 assets) returns uint256 { + //Using early proved rules (from bootcamp) uint256 shares = convertToShares(assets); require assets >= convertToAssets(shares); return shares; } - function convertToAssetsSummary(uint256 shares) returns uint256 { + //Using early proved rules (from bootcamp) uint256 assets = convertToAssets(shares); require shares >= convertToShares(assets); return assets; From 09980cbc0649d144247be1a051dfc32d7058b2ca Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 11:57:39 +0200 Subject: [PATCH 038/125] Results for run: https://prover.certora.com/output/53900/83a6d2f84c9543d6a87ef8ffb9b750e3?anonymousKey=c4d79c504f8ce2600980de9d6e860a1d3befe102 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- lesson4_reading/erc4626/gitCertoraRun.sh | 12 +++++++---- .../certora/specs/ERC4626-RoundingProps.spec | 21 ++----------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/lesson4_reading/erc4626/gitCertoraRun.sh b/lesson4_reading/erc4626/gitCertoraRun.sh index 09652b5..a78b45f 100755 --- a/lesson4_reading/erc4626/gitCertoraRun.sh +++ b/lesson4_reading/erc4626/gitCertoraRun.sh @@ -1,9 +1,13 @@ certoraOutput=$(certoraRun "$@") echo "$certoraOutput" -certoraURL=$(echo $certoraOutput | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p') -git add -A +if [[ ${certoraOutput} != *"ERROR"* ]];then -git commit -m "Results for run: $certoraURL with arguments $@" -echo $certoraURL \ No newline at end of file + certoraURL=$(echo $certoraOutput | sed -n 's/.*\(https:\/\/prover.certora.com\/output\/\)/\1/p') + + git add -A + + git commit -m "Results for run: $certoraURL with arguments $@" + echo $certoraURL +fi \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 298c21d..16f229e 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -39,7 +39,6 @@ ghost uint256 lastCallConvertToAssets_SharesParameter{ init_state axiom lastCallConvertToAssets_SharesParameter == 0; } - function convertToSharesSummary(uint256 assets) returns uint256 { lastCallConvertToShares_AssetsParameter = assets; uint256 convertedShares = convertToShares(assets); @@ -48,29 +47,13 @@ function convertToSharesSummary(uint256 assets) returns uint256 { } function convertToAssetsSummary(uint256 shares) returns uint256 { - lastCallToConvertToAssets_SharesParameter = shares; + lastCallConvertToAssets_SharesParameter = shares; uint256 convertedAssets = convertToAssets(shares); - require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedShares); + require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; } - - -function convertToSharesSummary(uint256 assets) returns uint256 { - //Using early proved rules (from bootcamp) - uint256 shares = convertToShares(assets); - require assets >= convertToAssets(shares); - return shares; -} - -function convertToAssetsSummary(uint256 shares) returns uint256 { - //Using early proved rules (from bootcamp) - uint256 assets = convertToAssets(shares); - require shares >= convertToShares(assets); - return assets; -} - function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ mathint totalSupply = totalSupply(); mathint balanceOfAddr1 = balanceOf(addr1); From beea1b65aa2a3b560122abaeb40966eb50ac64e7 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:04:05 +0200 Subject: [PATCH 039/125] Results for run: https://prover.certora.com/output/53900/2750eaf669154a17b2fbbcbbcbf35023?anonymousKey=923dc3f82ab8056bbf37805901394a8290fcaa59 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 16f229e..4b4554a 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -14,8 +14,8 @@ methods{ function convertToShares(uint256 assets) external returns uint256 envfree; function convertToAssets(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - function convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); - function convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); + function convertToShares(uint256 assets) external returns uint256 => convertToSharesSummary(assets); + function convertToAssets(uint256 shares) external returns uint256 => convertToAssetsSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; From e2fd043086a74ff22fdc71fb3a3fee1fb5f0525c Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:09:10 +0200 Subject: [PATCH 040/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../conf-openzeppelin/certoraRun-RoundingProps.conf | 1 + .../src/certora/specs/ERC4626-RoundingProps.spec | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index f560a0f..326fd75 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -1,6 +1,7 @@ { "files": [ "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/extensions/ERC4626.sol", "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], "verify": "ERC4626Mock:src/certora/specs/ERC4626-RoundingProps.spec", diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 4b4554a..b9de395 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -1,6 +1,7 @@ //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. using ERC20Mock as __ERC20; +using ERC4626 as __ERC4626; @@ -11,11 +12,9 @@ methods{ function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; - function convertToShares(uint256 assets) external returns uint256 envfree; - function convertToAssets(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - function convertToShares(uint256 assets) external returns uint256 => convertToSharesSummary(assets); - function convertToAssets(uint256 shares) external returns uint256 => convertToAssetsSummary(shares); + function __ERC4626._convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); + function __ERC4626._convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; @@ -41,14 +40,14 @@ ghost uint256 lastCallConvertToAssets_SharesParameter{ function convertToSharesSummary(uint256 assets) returns uint256 { lastCallConvertToShares_AssetsParameter = assets; - uint256 convertedShares = convertToShares(assets); + uint256 convertedShares = _convertToShares(assets); require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); return convertedShares; } function convertToAssetsSummary(uint256 shares) returns uint256 { lastCallConvertToAssets_SharesParameter = shares; - uint256 convertedAssets = convertToAssets(shares); + uint256 convertedAssets = _convertToAssets(shares); require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; From 4ad1233bff2d85bf4aeea0954f4b2b42c191abb4 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:09:54 +0200 Subject: [PATCH 041/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index 326fd75..42cca78 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -1,7 +1,7 @@ { "files": [ "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", - "src/openzeppelin-9ef69c0/contracts/mocks/token/extensions/ERC4626.sol", + "src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol", "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], "verify": "ERC4626Mock:src/certora/specs/ERC4626-RoundingProps.spec", From 3dbdfa317513dda2bc9965b1a4906add9e11149c Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:10:15 +0200 Subject: [PATCH 042/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../contracts/token/ERC20/extensions/ERC4626.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index adc4f66..2971850 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -45,7 +45,7 @@ import {Math} from "../../../utils/math/Math.sol"; * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. * ==== */ -abstract contract ERC4626 is ERC20, IERC4626 { +contract ERC4626 is ERC20, IERC4626 { using Math for uint256; IERC20 private immutable _asset; From 3d5f64cb3293606fe0e0bedc29cb737bc845da2c Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:10:48 +0200 Subject: [PATCH 043/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 5 ++--- .../contracts/token/ERC20/extensions/ERC4626.sol | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index b9de395..67631d0 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -1,7 +1,6 @@ //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. using ERC20Mock as __ERC20; -using ERC4626 as __ERC4626; @@ -13,8 +12,8 @@ methods{ function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - function __ERC4626._convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); - function __ERC4626._convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); + function _convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); + function _convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index 2971850..adc4f66 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -45,7 +45,7 @@ import {Math} from "../../../utils/math/Math.sol"; * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. * ==== */ -contract ERC4626 is ERC20, IERC4626 { +abstract contract ERC4626 is ERC20, IERC4626 { using Math for uint256; IERC20 private immutable _asset; From 7adb5b8ecdad746debcae6757ea65cbfc79a1e7f Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:12:53 +0200 Subject: [PATCH 044/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf | 1 - .../contracts/token/ERC20/extensions/ERC4626.sol | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index 42cca78..f560a0f 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -1,7 +1,6 @@ { "files": [ "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", - "src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol", "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], "verify": "ERC4626Mock:src/certora/specs/ERC4626-RoundingProps.spec", diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index adc4f66..2971850 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -45,7 +45,7 @@ import {Math} from "../../../utils/math/Math.sol"; * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. * ==== */ -abstract contract ERC4626 is ERC20, IERC4626 { +contract ERC4626 is ERC20, IERC4626 { using Math for uint256; IERC20 private immutable _asset; From a6ea314059c47200c3fd7c115281c3733a39c852 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:13:42 +0200 Subject: [PATCH 045/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../contracts/token/ERC20/extensions/ERC4626.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index 2971850..9453862 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -74,7 +74,7 @@ contract ERC4626 is ERC20, IERC4626 { /** * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ - constructor(IERC20 asset_) { + constructor(IERC20 asset_) ERC20("ERC20Mock", "E20M") { (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); _underlyingDecimals = success ? assetDecimals : 18; _asset = asset_; From 6c3008604f3dadc999e536dfaea4ac6e3363ec9f Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:13:58 +0200 Subject: [PATCH 046/125] Results for run: with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol index 39ab129..94763a5 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; contract ERC20Mock is ERC20 { - constructor() ERC20("ERC20Mock", "E20M") {} + constructor() {} function mint(address account, uint256 amount) external { _mint(account, amount); From f83a08cd1da4a56ec03b362b56803680de0f29dd Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:48:59 +0200 Subject: [PATCH 047/125] Results for run: https://prover.certora.com/output/53900/8ca2b34b271c4d8ba17ad5b5c9844ce0?anonymousKey=34a450baae87005a72a3405291999a7f64783531 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../certoraRun-RoundingProps.conf | 1 + .../src/certora/specs/ERC4626-RoundingProps.spec | 15 +++++++++------ .../contracts/mocks/token/ERC20Mock.sol | 2 +- .../contracts/mocks/token/ERC4626Mock.sol | 2 +- .../contracts/token/ERC20/extensions/ERC4626.sol | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index f560a0f..42cca78 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -1,6 +1,7 @@ { "files": [ "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol", "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], "verify": "ERC4626Mock:src/certora/specs/ERC4626-RoundingProps.spec", diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 67631d0..c86a93f 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -1,6 +1,7 @@ //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. using ERC20Mock as __ERC20; +using ERC4626 as _ERC4626; @@ -11,9 +12,11 @@ methods{ function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; + function previewWithdraw(uint256 assets) external returns uint256 envfree; + function previewMint(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - function _convertToShares(uint256 assets) internal returns uint256 => convertToSharesSummary(assets); - function _convertToAssets(uint256 shares) internal returns uint256 => convertToAssetsSummary(shares); + function previewWithdraw(uint256 assets) external returns uint256 => previewWithdrawSummary(assets); + function previewMint(uint256 shares) internal returns uint256 => previewMintSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; @@ -37,16 +40,16 @@ ghost uint256 lastCallConvertToAssets_SharesParameter{ init_state axiom lastCallConvertToAssets_SharesParameter == 0; } -function convertToSharesSummary(uint256 assets) returns uint256 { +function previewWithdrawSummary(uint256 assets) returns uint256 { lastCallConvertToShares_AssetsParameter = assets; - uint256 convertedShares = _convertToShares(assets); + uint256 convertedShares = previewWithdraw(assets); require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); return convertedShares; } -function convertToAssetsSummary(uint256 shares) returns uint256 { +function previewMintSummary(uint256 shares) returns uint256 { lastCallConvertToAssets_SharesParameter = shares; - uint256 convertedAssets = _convertToAssets(shares); + uint256 convertedAssets = previewMint(shares); require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol index 94763a5..39ab129 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; contract ERC20Mock is ERC20 { - constructor() {} + constructor() ERC20("ERC20Mock", "E20M") {} function mint(address account, uint256 amount) external { _mint(account, amount); diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol index 22ac5e8..5ce832e 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol @@ -5,7 +5,7 @@ import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; contract ERC4626Mock is ERC4626 { - constructor(address underlying) ERC20("ERC4626Mock", "E4626M") ERC4626(IERC20(underlying)) {} + constructor(address underlying) ERC4626(IERC20(underlying)) {} function mint(address account, uint256 amount) external { _mint(account, amount); diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index 9453862..97d1703 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -74,7 +74,7 @@ contract ERC4626 is ERC20, IERC4626 { /** * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ - constructor(IERC20 asset_) ERC20("ERC20Mock", "E20M") { + constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); _underlyingDecimals = success ? assetDecimals : 18; _asset = asset_; From 96651e91729a34237a584443e460dbcc0b9c9b3c Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:54:01 +0200 Subject: [PATCH 048/125] Results for run: https://prover.certora.com/output/53900/268822332c9d41cb8e57d40111fa4979?anonymousKey=4588986964b44e9aa8e9ed09438017a63a63daad with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../certora/conf-openzeppelin/certoraRun-RoundingProps.conf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index 42cca78..5880b4f 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -9,11 +9,12 @@ "link": ["ERC4626Mock:_asset=ERC20Mock"], "prover_args": [ "-smt_hashingScheme plainInjectivity" , - "-solvers [yices,z3]" + "-solvers [yices,z3]", + "-numOfUnsatCores 1" ], "server": "production", "send_only": true, - "rule_sanity": "basic", + "prover_version": "master", "msg": "Verification of ERC4626", "optimistic_loop": true, } \ No newline at end of file From c9accd41b455d111ecb72f2af360272c0a3f88bb Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:55:26 +0200 Subject: [PATCH 049/125] Results for run: https://prover.certora.com/output/53900/4ca47d9992fc4de7973f16c53ac68f29?anonymousKey=2dcf79425a110c93864149c65f9ca4539ec1b7de with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index c86a93f..20b27eb 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -43,7 +43,7 @@ ghost uint256 lastCallConvertToAssets_SharesParameter{ function previewWithdrawSummary(uint256 assets) returns uint256 { lastCallConvertToShares_AssetsParameter = assets; uint256 convertedShares = previewWithdraw(assets); - require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); + //require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); return convertedShares; } @@ -51,7 +51,7 @@ function previewMintSummary(uint256 shares) returns uint256 { lastCallConvertToAssets_SharesParameter = shares; uint256 convertedAssets = previewMint(shares); - require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); + //require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; } From 82891ad680cdb44115528a9cc30096895bc55968 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 12:57:41 +0200 Subject: [PATCH 050/125] Results for run: https://prover.certora.com/output/53900/a6cafe531a054f7eab7e39eade81f906?anonymousKey=0adf1cad6d283a58c874344c7f55a3ed1b5a88af with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf | 1 - .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 1 - 2 files changed, 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index 5880b4f..9a6c765 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -1,7 +1,6 @@ { "files": [ "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", - "src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol", "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], "verify": "ERC4626Mock:src/certora/specs/ERC4626-RoundingProps.spec", diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 20b27eb..9f3e5f0 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -1,7 +1,6 @@ //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. using ERC20Mock as __ERC20; -using ERC4626 as _ERC4626; From 871726018715eebd6d42a0773527ff210d81b3fb Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 13:03:06 +0200 Subject: [PATCH 051/125] Results for run: https://prover.certora.com/output/53900/da251e594eed421fa66171ed502604a6?anonymousKey=cfb759a575ada0b72ee21e26bb3417f0c2c61a65 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 9f3e5f0..500f6fd 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -15,7 +15,7 @@ methods{ function previewMint(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); function previewWithdraw(uint256 assets) external returns uint256 => previewWithdrawSummary(assets); - function previewMint(uint256 shares) internal returns uint256 => previewMintSummary(shares); + function previewMint(uint256 shares) external returns uint256 => previewMintSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; @@ -42,7 +42,7 @@ ghost uint256 lastCallConvertToAssets_SharesParameter{ function previewWithdrawSummary(uint256 assets) returns uint256 { lastCallConvertToShares_AssetsParameter = assets; uint256 convertedShares = previewWithdraw(assets); - //require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); + require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); return convertedShares; } @@ -50,7 +50,7 @@ function previewMintSummary(uint256 shares) returns uint256 { lastCallConvertToAssets_SharesParameter = shares; uint256 convertedAssets = previewMint(shares); - //require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); + require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; } From 6646cf886eb28d74d6b3baaaab07754a3cb65ff8 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 13:06:04 +0200 Subject: [PATCH 052/125] Results for run: https://prover.certora.com/output/53900/9aa41c62aaba41f089f9c564a1062d01?anonymousKey=c79bde0f0d332fe20db77709374250255761a32e with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 500f6fd..f0064ca 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -14,8 +14,8 @@ methods{ function previewWithdraw(uint256 assets) external returns uint256 envfree; function previewMint(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - function previewWithdraw(uint256 assets) external returns uint256 => previewWithdrawSummary(assets); - function previewMint(uint256 shares) external returns uint256 => previewMintSummary(shares); + function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); + function previewMint(uint256 shares) internal returns uint256 => previewMintSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; From d4460baf1947802f158897b914726da1b663e28d Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 14:12:58 +0200 Subject: [PATCH 053/125] Results for run: https://prover.certora.com/output/53900/5b88926b99044166a561bf1e4939f10d?anonymousKey=e0f11ac9da0e94d27c22434b601434fb755034be with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index f0064ca..136e548 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -19,7 +19,7 @@ methods{ } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - // require(res * denominator) <= x * y; + require(res * denominator) <= x * y; require((res + 1) * denominator) > x * y; require x <= denominator; From 8d03d84715e5a8309c25898fb97b5e94a8f7e293 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 14:13:46 +0200 Subject: [PATCH 054/125] Results for run: https://prover.certora.com/output/53900/e38d93333f674534bd57cc415fd0d49a?anonymousKey=8747736e1a3464adf39eecb716772628d0c33ee6 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../certora/conf-openzeppelin/certoraRun-RoundingProps.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index 9a6c765..c230d81 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -9,10 +9,10 @@ "prover_args": [ "-smt_hashingScheme plainInjectivity" , "-solvers [yices,z3]", - "-numOfUnsatCores 1" ], "server": "production", - "send_only": true, + "send_only": true, + "rule_sanity": "basic", "prover_version": "master", "msg": "Verification of ERC4626", "optimistic_loop": true, From 505d39d1eca11ae9f4a8598fac6c9edbe38ebc5a Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 14:16:18 +0200 Subject: [PATCH 055/125] Results for run: https://prover.certora.com/output/53900/3cca5db414ec4faba265bd90609cf5ed?anonymousKey=e69dc517c80b197f4eab86b8beb04f42dcfd3aeb with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 136e548..95f1a35 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -50,7 +50,7 @@ function previewMintSummary(uint256 shares) returns uint256 { lastCallConvertToAssets_SharesParameter = shares; uint256 convertedAssets = previewMint(shares); - require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); + // require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; } From 579c135d48845b210e6edb014ae2e226f2a4819d Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 14:18:58 +0200 Subject: [PATCH 056/125] Results for run: https://prover.certora.com/output/53900/00fd75155c45477fad1a8cdaa3e4b513?anonymousKey=53c426c9d94878876d1e64995789a7923066b301 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 95f1a35..ce25c02 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -19,8 +19,8 @@ methods{ } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; + // require(res * denominator) <= x * y; + // require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; @@ -50,7 +50,7 @@ function previewMintSummary(uint256 shares) returns uint256 { lastCallConvertToAssets_SharesParameter = shares; uint256 convertedAssets = previewMint(shares); - // require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); + require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; } From 623a5e9c5e44ca5f715188570885b875e2319ebb Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 14:20:58 +0200 Subject: [PATCH 057/125] Results for run: https://prover.certora.com/output/53900/105b62c4c03d407691af8fb859a787d6?anonymousKey=9a201428fab0275f3ef0d31cdbd8b13caed8c1e1 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index ce25c02..694b954 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -42,7 +42,7 @@ ghost uint256 lastCallConvertToAssets_SharesParameter{ function previewWithdrawSummary(uint256 assets) returns uint256 { lastCallConvertToShares_AssetsParameter = assets; uint256 convertedShares = previewWithdraw(assets); - require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); + //require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); return convertedShares; } @@ -50,7 +50,7 @@ function previewMintSummary(uint256 shares) returns uint256 { lastCallConvertToAssets_SharesParameter = shares; uint256 convertedAssets = previewMint(shares); - require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); + // require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; } From 81d00c2f87dc9a466cc067de44a8fae83703e005 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 16:38:24 +0200 Subject: [PATCH 058/125] Results for run: https://prover.certora.com/output/53900/493b65f0aa0f484ea2d62e2ae963da09?anonymousKey=a236ed8007fd92ee878727584f04910ce17bb875 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../src/certora/specs/ERC4626-RoundingProps.spec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 694b954..3e8ccdc 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -14,19 +14,19 @@ methods{ function previewWithdraw(uint256 assets) external returns uint256 envfree; function previewMint(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); - function previewMint(uint256 shares) internal returns uint256 => previewMintSummary(shares); + //function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); + //function previewMint(uint256 shares) internal returns uint256 => previewMintSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; // require(res * denominator) <= x * y; // require((res + 1) * denominator) > x * y; - +/* require x <= denominator; require res <= y; require x == 0 => res == 0; - require denominator > 0; - return res; + require denominator > 0;*/ + return require_uint256(x * y / denominator); } From ef1d14ba510bf0afc68813411f54b259342334c8 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 16:43:19 +0200 Subject: [PATCH 059/125] Results for run: https://prover.certora.com/output/53900/5abad2f620394ed9af3fffe226208e6f?anonymousKey=9fb4d5930039a6395813debeb7d256a9d636425b with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 3e8ccdc..53f4f4f 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -19,14 +19,15 @@ methods{ } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - // require(res * denominator) <= x * y; - // require((res + 1) * denominator) > x * y; + require(res * denominator) <= x * y; + require((res + 1) * denominator) > x * y; /* require x <= denominator; require res <= y; require x == 0 => res == 0; require denominator > 0;*/ - return require_uint256(x * y / denominator); + return res; + // return require_uint256(x * y / denominator); } From ddb04c202db43b004b0a2954a27992f395d89734 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 17:07:50 +0200 Subject: [PATCH 060/125] Results for run: https://prover.certora.com/output/53900/60dc77e24f7c4b98a0ee8585ab01cd0a?anonymousKey=64ca1cd55d6fc1815b129801c36498096d1bff4f with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 53f4f4f..c342e78 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -88,7 +88,12 @@ function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,addres } function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address addr3, address addr4){ + mathint totalSupply = __ERC20.totalSupply(); + + if(addr1 == __ERC20 || addr2 == __ERC20  || addr3 == __ERC20 || addr4 == __ERC20){ + totalSupply = totalSupply - balanceOf(__ERC20); + } mathint balanceOfAddr1 = __ERC20.balanceOf(addr1); mathint balanceOfAddr2 = __ERC20.balanceOf(addr2); mathint balanceOfAddr3 = __ERC20.balanceOf(addr3); From 1e24777824d4bf4285f04f6a0d6f147154d497f4 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 17:10:44 +0200 Subject: [PATCH 061/125] Results for run: https://prover.certora.com/output/53900/c83bbaf949b643319e883fff172fc813?anonymousKey=8904a6f0a6956af2807d484b6845491182b548ad with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index c342e78..8d28613 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -91,8 +91,8 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address mathint totalSupply = __ERC20.totalSupply(); - if(addr1 == __ERC20 || addr2 == __ERC20  || addr3 == __ERC20 || addr4 == __ERC20){ - totalSupply = totalSupply - balanceOf(__ERC20); + if(addr1 != currentContract || addr2 != currentContract  || addr3 != currentContract || addr4 != currentContract){ + totalSupply = totalSupply - balanceOf(currentContract); } mathint balanceOfAddr1 = __ERC20.balanceOf(addr1); mathint balanceOfAddr2 = __ERC20.balanceOf(addr2); From e8d558709226a962e502beeadc7f033707b3de18 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 17:14:16 +0200 Subject: [PATCH 062/125] Results for run: https://prover.certora.com/output/53900/2f76750d0fe54b51accfd6e5ce00d218?anonymousKey=f1018938f3b06dd59e7ca555d923b60eb3cda3c4 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 8d28613..937f1d3 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -91,7 +91,7 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address mathint totalSupply = __ERC20.totalSupply(); - if(addr1 != currentContract || addr2 != currentContract  || addr3 != currentContract || addr4 != currentContract){ + if(addr1 != currentContract && addr2 != currentContract  && addr3 != currentContract && addr4 != currentContract){ totalSupply = totalSupply - balanceOf(currentContract); } mathint balanceOfAddr1 = __ERC20.balanceOf(addr1); From 6b316cb2f67b46a8e9df06ad00be0c7f547938ed Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 17:41:33 +0200 Subject: [PATCH 063/125] Results for run: https://prover.certora.com/output/53900/cd89f6f14ec64ed28addb2430a827207?anonymousKey=8ab0f7093bfe3bce197278c9c1a0c1c5fa86252c with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../certora/specs/ERC4626-RoundingProps.spec | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 937f1d3..d92b4bb 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -147,3 +147,28 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } + + +rule inverseRedeemWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + //TODO introduce a fifths person... != withdraw_owner + uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} \ No newline at end of file From 6aa21b8dfc1afc0956cf9f2e6d8bfde081c152f2 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 17:44:02 +0200 Subject: [PATCH 064/125] Results for run: https://prover.certora.com/output/53900/46bebf5030a643939e6e64630ca83ca2?anonymousKey=32acbd393c9f3c46df3691b71bb2d26409d637a7 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index d92b4bb..ffdd1b8 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -18,7 +18,7 @@ methods{ //function previewMint(uint256 shares) internal returns uint256 => previewMintSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; + /* uint256 res; require(res * denominator) <= x * y; require((res + 1) * denominator) > x * y; /* @@ -26,8 +26,8 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 require res <= y; require x == 0 => res == 0; require denominator > 0;*/ - return res; - // return require_uint256(x * y / denominator); + //return res; + return require_uint256(x * y / denominator); } From c49cae691c449f58700d012f92ac9300dc406eca Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 18:07:13 +0200 Subject: [PATCH 065/125] Results for run: https://prover.certora.com/output/53900/1699f9e968b94d0495856a19cf84634f?anonymousKey=89f2382b116e169156166cfd2ed73767c3bb77ba with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index ffdd1b8..fae3490 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -18,7 +18,7 @@ methods{ //function previewMint(uint256 shares) internal returns uint256 => previewMintSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - /* uint256 res; + uint256 res; require(res * denominator) <= x * y; require((res + 1) * denominator) > x * y; /* @@ -26,8 +26,8 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 require res <= y; require x == 0 => res == 0; require denominator > 0;*/ - //return res; - return require_uint256(x * y / denominator); + return res; + //return require_uint256(x * y / denominator); } From 4139295ed2d05c4de94d08ebae9705cac03fa963 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 18:12:03 +0200 Subject: [PATCH 066/125] Results for run: https://prover.certora.com/output/53900/553bffcb4d094f428ab4c2d78974142c?anonymousKey=7c13b9f939f803c0a564758bfa7c5726a2049d0b with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../certora/specs/ERC4626-RoundingProps.spec | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index fae3490..0c6b88c 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -12,20 +12,20 @@ methods{ function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; function previewWithdraw(uint256 assets) external returns uint256 envfree; - function previewMint(uint256 shares) external returns uint256 envfree; + function previewRedeem(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - //function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); - //function previewMint(uint256 shares) internal returns uint256 => previewMintSummary(shares); + function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); + function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; -/* +// require(res * denominator) <= x * y; +// require((res + 1) * denominator) > x * y; + require x <= denominator; require res <= y; require x == 0 => res == 0; - require denominator > 0;*/ + require denominator > 0; return res; //return require_uint256(x * y / denominator); } @@ -43,15 +43,15 @@ ghost uint256 lastCallConvertToAssets_SharesParameter{ function previewWithdrawSummary(uint256 assets) returns uint256 { lastCallConvertToShares_AssetsParameter = assets; uint256 convertedShares = previewWithdraw(assets); - //require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); + require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); return convertedShares; } -function previewMintSummary(uint256 shares) returns uint256 { +function previewRedeemSummary(uint256 shares) returns uint256 { lastCallConvertToAssets_SharesParameter = shares; - uint256 convertedAssets = previewMint(shares); + uint256 convertedAssets = previewRedeem(shares); - // require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); + require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); return convertedAssets; } @@ -123,7 +123,7 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; } - + /* rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); @@ -146,7 +146,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; -} +}*/ rule inverseRedeemWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ From d06bfd0c8b957222bc5707d71311683965388982 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 18:17:08 +0200 Subject: [PATCH 067/125] Results for run: https://prover.certora.com/output/53900/5d8ec456a7784fa7acc44de0374058c6?anonymousKey=54a49fa43a8488abfde5c3755c68ea436a4c87ee with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 0c6b88c..2f9b274 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -14,8 +14,8 @@ methods{ function previewWithdraw(uint256 assets) external returns uint256 envfree; function previewRedeem(uint256 shares) external returns uint256 envfree; function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); - function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); + //function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); + //function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; From c4e0002b41b558e6941ae2df6a39ce124f22f9aa Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 18:21:28 +0200 Subject: [PATCH 068/125] Results for run: https://prover.certora.com/output/53900/c8d62c56301242b6a490d2d3563e30af?anonymousKey=04e2be7107f79960342748e898b24829c851f346 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 2f9b274..6493e9e 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -92,7 +92,7 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address mathint totalSupply = __ERC20.totalSupply(); if(addr1 != currentContract && addr2 != currentContract  && addr3 != currentContract && addr4 != currentContract){ - totalSupply = totalSupply - balanceOf(currentContract); + totalSupply = totalSupply - __ERC20.balanceOf(currentContract); } mathint balanceOfAddr1 = __ERC20.balanceOf(addr1); mathint balanceOfAddr2 = __ERC20.balanceOf(addr2); From 8d8659de1e965b229386d0d8bd174d396ba1ffb3 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 15 Sep 2023 18:21:51 +0200 Subject: [PATCH 069/125] Results for run: https://prover.certora.com/output/53900/c07026327711423ebc250874de129a0d?anonymousKey=e47d40483ad0070868c02912d2689cca056e3b79 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../src/certora/specs/ERC4626-RoundingProps.spec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 6493e9e..42d48ab 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -18,7 +18,7 @@ methods{ //function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; + /* uint256 res; // require(res * denominator) <= x * y; // require((res + 1) * denominator) > x * y; @@ -26,8 +26,8 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 require res <= y; require x == 0 => res == 0; require denominator > 0; - return res; - //return require_uint256(x * y / denominator); + return res;*/ + return require_uint256(x * y / denominator); } @@ -123,7 +123,7 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; } - /* + rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); @@ -146,7 +146,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; -}*/ +} rule inverseRedeemWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ From 69da8247d692dc6d9c5ccba7595423255ff83e8d Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:12:57 +0200 Subject: [PATCH 070/125] Results for run: https://prover.certora.com/output/53900/878ce98bfb704d4b9b58defa3c0ea7bb?anonymousKey=d815027bcf06818470c7480c4843680ffe0cd305 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 42d48ab..481fd35 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -130,7 +130,8 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); //Dismiss allowance case - require(e.msg.sender == withdraw_owner); + //"Activating" allowance case by removing this requires + //require(e.msg.sender == withdraw_owner); //Make all non zero to avoid unnecessary cases. require(e.msg.sender != 0); From 87350c92e94ecfa2515172d35d0be67acbe7b599 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:28:09 +0200 Subject: [PATCH 071/125] Adding github action --- .github/workflows/certora.yml | 109 ++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 .github/workflows/certora.yml diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml new file mode 100644 index 0000000..8ab548c --- /dev/null +++ b/.github/workflows/certora.yml @@ -0,0 +1,109 @@ +# A workflow file for running Certora verification through github actions. +# Find results for each push in the "Actions" tab on the github website. +name: Certora verification + +on: + push: {} + pull_request: {} + workflow_dispatch: {} + +jobs: + verify: + runs-on: ubuntu-latest + steps: + # check out the current version + - uses: actions/checkout@v2 + + # install Certora dependencies and CLI + - name: Install python + uses: actions/setup-python@v2 + with: + python-version: '3.10' + # cache: 'pip' + - name: Install certora + run: pip3 install certora-cli-beta + + # the following is only necessary if your project depends on +contracts + # installed using yarn + # - name: Install yarn + # uses: actions/setup-node@v3 + # with: + # node-version: 16 + # cache: 'yarn' + # - name: Install dependencies + # run: yarn + + # Install the appropriate version of solc + - name: Install solc + run: | + wget +https://github.com/ethereum/solidity/releases/download/v0.8.0/solc-static-linux + sudo mv solc-static-linux /usr/local/bin/solc8.0 + chmod +x /usr/local/bin/solc8.0 + + wget +https://github.com/ethereum/solidity/releases/download/v0.8.20/solc-static-linux + sudo mv solc-static-linux /usr/local/bin/solc8.20 + chmod +x /usr/local/bin/solc8.20 + + # Do the actual verification. The `run` field could be simply + # + # certoraRun certora/conf/${{ matrix.params }} + # + # but we do a little extra work to get the commit messages into the + # `--msg` argument to `certoraRun` + # + # Here ${{ matrix.params }} gets replaced with each of the +parameters + # listed in the `params` section below. + - name: Verify rule ${{ matrix.params.name }} + run: > + message="$(git log -n 1 --pretty=format:'CI +${{matrix.params.name}} %h .... %s')"; + certoraRun \ + certora/conf/${{ matrix.params.command }} \ + --msg "$(echo $message | sed 's/[^a-zA-Z0-9., _-]/ /g')" + env: + # For this to work, you must set your CERTORAKEY secret on the +github + # website (settings > secrets > actions > new repository secret) + CERTORAKEY: ${{ secrets.CERTORAKEY }} + + # The following two steps save the output json as a github artifact. + # This can be useful for automation that collects the output. + - name: Download output json + if: always() + run: > + outputLink=$(sed 's/zipOutput/output/g' .zip-output-url.txt | +sed 's/?/\/output.json?/g'); + curl -L -b "certoraKey=$CERTORAKEY;" ${outputLink} --output +output.json || true; + touch output.json; + + - name: Archive output json + if: always() + uses: actions/upload-artifact@v3 + with: + name: output for ${{ matrix.params.name }} + path: output.json + + strategy: + fail-fast: false + max-parallel: 4 + matrix: + params: + # each of these commands is passed to the "Verify rule" step above, + # which runs certoraRun on certora/conf/ + # + # Note that each of these lines will appear as a separate run on + # prover.certora.com + # + # It is often helpful to split up by rule or even by method for a + # parametric rule, although it is certainly possible to run everything + # at once by not passing the `--rule` or `--method` options + #- {name: transferSpec, command: 'ERC20.conf --rule transferSpec'} + #- {name: generalRulesOnERC20, command: 'generalRules_ERC20.conf --debug'} + #- {name: generalRulesOnVAULT, command: 'generalRules_VAULT.conf --debug'} + - {name: RulesForERC4626, command: 'lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --debug'} + From cf15714f897e5abf382f8c9f9ed4a9576ce67a01 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:30:08 +0200 Subject: [PATCH 072/125] Results for run: https://prover.certora.com/output/53900/dc0a906fad22482a88ea5ee7402898a8?anonymousKey=667f5ae97b65a5500a562af9184cf808aeac0b67 with arguments src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 481fd35..eafdd3b 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -157,6 +157,9 @@ rule inverseRedeemWithdrawInFavourForVault(uint256 shares, address mint_receiver //Dismiss allowance case require(e.msg.sender == withdraw_owner); + //Further restrict arguments: Now every address is equal to e.msg.sender. + require(e.msg.sender == mint_receiver); + require(e.msg.sender == withdraw_receiver); //Make all non zero to avoid unnecessary cases. require(e.msg.sender != 0); From 067944785c731d6061977b3499de9a7a6440eb1b Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:31:26 +0200 Subject: [PATCH 073/125] Fixing github action --- .github/workflows/certora.yml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml index 8ab548c..f280ed0 100644 --- a/.github/workflows/certora.yml +++ b/.github/workflows/certora.yml @@ -23,8 +23,7 @@ jobs: - name: Install certora run: pip3 install certora-cli-beta - # the following is only necessary if your project depends on -contracts + # the following is only necessary if your project depends on contracts # installed using yarn # - name: Install yarn # uses: actions/setup-node@v3 @@ -37,13 +36,11 @@ contracts # Install the appropriate version of solc - name: Install solc run: | - wget -https://github.com/ethereum/solidity/releases/download/v0.8.0/solc-static-linux + wget https://github.com/ethereum/solidity/releases/download/v0.8.0/solc-static-linux sudo mv solc-static-linux /usr/local/bin/solc8.0 chmod +x /usr/local/bin/solc8.0 - wget -https://github.com/ethereum/solidity/releases/download/v0.8.20/solc-static-linux + wget https://github.com/ethereum/solidity/releases/download/v0.8.20/solc-static-linux sudo mv solc-static-linux /usr/local/bin/solc8.20 chmod +x /usr/local/bin/solc8.20 @@ -54,19 +51,16 @@ https://github.com/ethereum/solidity/releases/download/v0.8.20/solc-static-linux # but we do a little extra work to get the commit messages into the # `--msg` argument to `certoraRun` # - # Here ${{ matrix.params }} gets replaced with each of the -parameters + # Here ${{ matrix.params }} gets replaced with each of the parameters # listed in the `params` section below. - name: Verify rule ${{ matrix.params.name }} run: > - message="$(git log -n 1 --pretty=format:'CI -${{matrix.params.name}} %h .... %s')"; + message="$(git log -n 1 --pretty=format:'CI ${{matrix.params.name}} %h .... %s')"; certoraRun \ certora/conf/${{ matrix.params.command }} \ --msg "$(echo $message | sed 's/[^a-zA-Z0-9., _-]/ /g')" env: - # For this to work, you must set your CERTORAKEY secret on the -github + # For this to work, you must set your CERTORAKEY secret on the github # website (settings > secrets > actions > new repository secret) CERTORAKEY: ${{ secrets.CERTORAKEY }} @@ -75,10 +69,8 @@ github - name: Download output json if: always() run: > - outputLink=$(sed 's/zipOutput/output/g' .zip-output-url.txt | -sed 's/?/\/output.json?/g'); - curl -L -b "certoraKey=$CERTORAKEY;" ${outputLink} --output -output.json || true; + outputLink=$(sed 's/zipOutput/output/g' .zip-output-url.txt | sed 's/?/\/output.json?/g'); + curl -L -b "certoraKey=$CERTORAKEY;" ${outputLink} --output output.json || true; touch output.json; - name: Archive output json From 3f3e57abcc493da229627c75b87525a27e54f188 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:34:13 +0200 Subject: [PATCH 074/125] Concretizing values --- .../certora/specs/ERC4626-RoundingProps.spec | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index eafdd3b..9e722ab 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -150,7 +150,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address } -rule inverseRedeemWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseRedeemWithdrawInFavourForVault_ConcretizedValues(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); @@ -174,5 +174,61 @@ rule inverseRedeemWithdrawInFavourForVault(uint256 shares, address mint_receiver uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} + + + +rule inverseRedeemWithdrawInFavourForVault_MintReceiverEqualMessageSender(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); + //Further restrict arguments: Now every address is equal to e.msg.sender. + require(e.msg.sender == mint_receiver); + //require(e.msg.sender == withdraw_receiver); + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + //TODO introduce a fifths person... != withdraw_owner + uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} + +rule inverseRedeemWithdrawInFavourForVault_WithdrawReceiverEqualMessageSender(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); + //Further restrict arguments: Now every address is equal to e.msg.sender. + //require(e.msg.sender == mint_receiver); + require(e.msg.sender == withdraw_receiver); + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + //TODO introduce a fifths person... != withdraw_owner + uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } \ No newline at end of file From 4458df5cc60f2fe7a88ec89465d6a6bcf3cbb1f8 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:35:15 +0200 Subject: [PATCH 075/125] Fixing github action --- .github/workflows/certora.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml index f280ed0..e373c5f 100644 --- a/.github/workflows/certora.yml +++ b/.github/workflows/certora.yml @@ -57,7 +57,7 @@ jobs: run: > message="$(git log -n 1 --pretty=format:'CI ${{matrix.params.name}} %h .... %s')"; certoraRun \ - certora/conf/${{ matrix.params.command }} \ + ${{ matrix.params.command }} \ --msg "$(echo $message | sed 's/[^a-zA-Z0-9., _-]/ /g')" env: # For this to work, you must set your CERTORAKEY secret on the github From f56040fe1d2d915807609f1a7dc2341993425aca Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:38:39 +0200 Subject: [PATCH 076/125] Fixing github action --- .github/workflows/certora.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml index e373c5f..bf6b968 100644 --- a/.github/workflows/certora.yml +++ b/.github/workflows/certora.yml @@ -56,7 +56,7 @@ jobs: - name: Verify rule ${{ matrix.params.name }} run: > message="$(git log -n 1 --pretty=format:'CI ${{matrix.params.name}} %h .... %s')"; - certoraRun \ + cd lesson4_reading/erc4626/ && certoraRun \ ${{ matrix.params.command }} \ --msg "$(echo $message | sed 's/[^a-zA-Z0-9., _-]/ /g')" env: @@ -97,5 +97,5 @@ jobs: #- {name: transferSpec, command: 'ERC20.conf --rule transferSpec'} #- {name: generalRulesOnERC20, command: 'generalRules_ERC20.conf --debug'} #- {name: generalRulesOnVAULT, command: 'generalRules_VAULT.conf --debug'} - - {name: RulesForERC4626, command: 'lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --debug'} + - {name: RulesForERC4626, command: 'src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --debug'} From 6aeb0350b29ea208d05b2e62208ceb53e9248a5b Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:47:54 +0200 Subject: [PATCH 077/125] Fix receivers --- .../certora/specs/ERC4626-RoundingProps.spec | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 9e722ab..e352944 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -230,5 +230,32 @@ rule inverseRedeemWithdrawInFavourForVault_WithdrawReceiverEqualMessageSender(ui uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} + +rule inverseRedeemWithdrawInFavourForVault_FixReceivers(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + //require(e.msg.sender == withdraw_owner); + //Further restrict arguments: Now every address is equal to e.msg.sender. + require(e.msg.sender == mint_receiver); + require(e.msg.sender == withdraw_receiver); + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + //TODO introduce a fifths person... != withdraw_owner + uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } \ No newline at end of file From 9c37d73f332bc963ac194d588071d9b1f542a940 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:49:58 +0200 Subject: [PATCH 078/125] Simplifying withdraw logic (munging) --- .../contracts/token/ERC20/extensions/ERC4626.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index 97d1703..3ac69a1 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -275,7 +275,7 @@ contract ERC4626 is ERC20, IERC4626 { // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the // shares are burned and after the assets are transferred, which is a valid state. _burn(owner, shares); - SafeERC20.safeTransfer(_asset, receiver, assets); + _asset.transfer(receiver, assets); emit Withdraw(caller, receiver, owner, assets, shares); } From 6c6b949c2fd3f8bb99c1fabd9d37ff947d120b67 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 09:57:36 +0200 Subject: [PATCH 079/125] Using non beta version of certora --- .github/workflows/certora.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml index bf6b968..73d4705 100644 --- a/.github/workflows/certora.yml +++ b/.github/workflows/certora.yml @@ -21,7 +21,7 @@ jobs: python-version: '3.10' # cache: 'pip' - name: Install certora - run: pip3 install certora-cli-beta + run: pip3 install certora-cli # the following is only necessary if your project depends on contracts # installed using yarn From 4898296ad0572ce581c50b25e6bb363d288705e7 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 10:10:23 +0200 Subject: [PATCH 080/125] Removing non-linear math in function summary --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index e352944..44fda0f 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -21,13 +21,13 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 /* uint256 res; // require(res * denominator) <= x * y; // require((res + 1) * denominator) > x * y; - +*/ require x <= denominator; require res <= y; require x == 0 => res == 0; require denominator > 0; - return res;*/ - return require_uint256(x * y / denominator); + return res; + //return require_uint256(x * y / denominator); } From 5d63179b85a42c0647d739a4ec497568cb47cc7a Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 10:16:58 +0200 Subject: [PATCH 081/125] Removing non-linear math in function summary --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 44fda0f..b47c5fb 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -18,8 +18,8 @@ methods{ //function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - /* uint256 res; -// require(res * denominator) <= x * y; + uint256 res; +/* // require(res * denominator) <= x * y; // require((res + 1) * denominator) > x * y; */ require x <= denominator; From bc417c3f9c9d5929772ce7040c9d45d90b4aa539 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 10:27:34 +0200 Subject: [PATCH 082/125] Making mulDiv summary more precise --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index b47c5fb..15a8ca7 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -22,6 +22,8 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 /* // require(res * denominator) <= x * y; // require((res + 1) * denominator) > x * y; */ + + require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; require x == 0 => res == 0; From a76fb56e768ca363734e1863ba6802c3b41e74f1 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 10:32:28 +0200 Subject: [PATCH 083/125] Adding upper bound on mulDiv summary --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 15a8ca7..b1dcdea 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -23,6 +23,7 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 // require((res + 1) * denominator) > x * y; */ + require(res * denominator) <= x * y; require((res + 1) * denominator) > x * y; require x <= denominator; require res <= y; From d84b2f98f0c53f9d9b524b1f5882291cad27af25 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 10:33:34 +0200 Subject: [PATCH 084/125] Relaxing lower bound --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index b1dcdea..9ac266a 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -24,7 +24,7 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 */ require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; + require((res + 1) * denominator) >= x * y; require x <= denominator; require res <= y; require x == 0 => res == 0; From 023634f29135227a7e0efa0bd985395652bad855 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 10:40:52 +0200 Subject: [PATCH 085/125] concretizing input share values --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 9ac266a..eeaa2d5 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -229,6 +229,8 @@ rule inverseRedeemWithdrawInFavourForVault_WithdrawReceiverEqualMessageSender(ui require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); + require(shares == 1000); + //TODO introduce a fifths person... != withdraw_owner uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); @@ -256,6 +258,8 @@ rule inverseRedeemWithdrawInFavourForVault_FixReceivers(uint256 shares, address require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); + require(shares == 100); + //TODO introduce a fifths person... != withdraw_owner uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From 4c354071906fbc49b4301e2b9f583c4cc995dd85 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 10:52:39 +0200 Subject: [PATCH 086/125] Shares in range of 1 to 10.000 --- .../certora/specs/ERC4626-RoundingProps.spec | 61 +------------------ 1 file changed, 3 insertions(+), 58 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index eeaa2d5..b8d9f06 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -182,33 +182,6 @@ rule inverseRedeemWithdrawInFavourForVault_ConcretizedValues(uint256 shares, add -rule inverseRedeemWithdrawInFavourForVault_MintReceiverEqualMessageSender(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ - env e; - assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - - //Dismiss allowance case - require(e.msg.sender == withdraw_owner); - //Further restrict arguments: Now every address is equal to e.msg.sender. - require(e.msg.sender == mint_receiver); - //require(e.msg.sender == withdraw_receiver); - - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); - - require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); - - //TODO introduce a fifths person... != withdraw_owner - uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; -} - rule inverseRedeemWithdrawInFavourForVault_WithdrawReceiverEqualMessageSender(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); @@ -218,36 +191,7 @@ rule inverseRedeemWithdrawInFavourForVault_WithdrawReceiverEqualMessageSender(ui require(e.msg.sender == withdraw_owner); //Further restrict arguments: Now every address is equal to e.msg.sender. //require(e.msg.sender == mint_receiver); - require(e.msg.sender == withdraw_receiver); - - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); - - require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); - - require(shares == 1000); - - //TODO introduce a fifths person... != withdraw_owner - uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; -} - -rule inverseRedeemWithdrawInFavourForVault_FixReceivers(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ - env e; - assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - - //Dismiss allowance case - //require(e.msg.sender == withdraw_owner); - //Further restrict arguments: Now every address is equal to e.msg.sender. - require(e.msg.sender == mint_receiver); - require(e.msg.sender == withdraw_receiver); + // require(e.msg.sender == withdraw_receiver); //Make all non zero to avoid unnecessary cases. require(e.msg.sender != 0); @@ -258,8 +202,9 @@ rule inverseRedeemWithdrawInFavourForVault_FixReceivers(uint256 shares, address require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); - require(shares == 100); + require(shares > 1); + require(shares < 10000); //TODO introduce a fifths person... != withdraw_owner uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From d1e5dd92721f5958eefc2a704186b0d6fea720d9 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 11:00:22 +0200 Subject: [PATCH 087/125] Select 3 values for shares, allow allowance case --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index b8d9f06..4e8e76d 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -182,7 +182,7 @@ rule inverseRedeemWithdrawInFavourForVault_ConcretizedValues(uint256 shares, add -rule inverseRedeemWithdrawInFavourForVault_WithdrawReceiverEqualMessageSender(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseRedeemWithdrawInFavourForVault_ConcretizedShares(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); @@ -202,9 +202,9 @@ rule inverseRedeemWithdrawInFavourForVault_WithdrawReceiverEqualMessageSender(ui require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); - require(shares > 1); + - require(shares < 10000); + require(shares == 10000 || shares == 100000 || shares == 0xffff); //TODO introduce a fifths person... != withdraw_owner uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From 64ce5d8b9d18d957d4f138139058c96d6a19cd0a Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 11:09:29 +0200 Subject: [PATCH 088/125] Parrallel proving OpenZeppelin and base implementation // require shares to be larger than 0 --- .github/workflows/certora.yml | 1 + .../certoraRun-RoundingProps.conf | 2 +- .../ERC4626-RoundingProps.spec | 241 ++++++++++++++++++ .../certora/specs/ERC4626-RoundingProps.spec | 30 ++- 4 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml index 73d4705..2bccc49 100644 --- a/.github/workflows/certora.yml +++ b/.github/workflows/certora.yml @@ -97,5 +97,6 @@ jobs: #- {name: transferSpec, command: 'ERC20.conf --rule transferSpec'} #- {name: generalRulesOnERC20, command: 'generalRules_ERC20.conf --debug'} #- {name: generalRulesOnVAULT, command: 'generalRules_VAULT.conf --debug'} + - {name: RulesForERC4626, command: 'src/certora/conf/certoraRun-RoundingProps.conf --debug'} - {name: RulesForERC4626, command: 'src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --debug'} diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index c230d81..45aa501 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -3,7 +3,7 @@ "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], - "verify": "ERC4626Mock:src/certora/specs/ERC4626-RoundingProps.spec", + "verify": "ERC4626Mock:src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec", "solc": "solc8.20", "link": ["ERC4626Mock:_asset=ERC20Mock"], "prover_args": [ diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec new file mode 100644 index 0000000..aaebf29 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -0,0 +1,241 @@ + +//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. +using ERC20Mock as __ERC20; + + + +methods{ + function balanceOf(address) external returns uint256 envfree; + function __ERC20.balanceOf(address) external returns uint256 envfree; + function __ERC20.totalSupply() external returns uint256 envfree; + function decimals() external returns uint8 envfree; + function totalAssets() external returns uint256 envfree; + function totalSupply() external returns uint256 envfree; + function previewWithdraw(uint256 assets) external returns uint256 envfree; + function previewRedeem(uint256 shares) external returns uint256 envfree; + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); + //function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); + //function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); +} +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { + uint256 res; +/* // require(res * denominator) <= x * y; +// require((res + 1) * denominator) > x * y; +*/ + + require(res * denominator) <= x * y; + require((res + 1) * denominator) >= x * y; + require x <= denominator; + require res <= y; + require x == 0 => res == 0; + require denominator > 0; + return res; + //return require_uint256(x * y / denominator); +} + + +//The ghost is supposed to cover the equation: assets >= converToAssets(convertToShares(assets)). +ghost uint256 lastCallConvertToShares_AssetsParameter{ + init_state axiom lastCallConvertToShares_AssetsParameter == 0; +} +//The ghost is supposed to cover the equation: shares >= convertToShares(convertToAssets(shares)) +ghost uint256 lastCallConvertToAssets_SharesParameter{ + init_state axiom lastCallConvertToAssets_SharesParameter == 0; +} + +function previewWithdrawSummary(uint256 assets) returns uint256 { + lastCallConvertToShares_AssetsParameter = assets; + uint256 convertedShares = previewWithdraw(assets); + require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); + return convertedShares; +} + +function previewRedeemSummary(uint256 shares) returns uint256 { + lastCallConvertToAssets_SharesParameter = shares; + uint256 convertedAssets = previewRedeem(shares); + + require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); + return convertedAssets; +} + +function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ + mathint totalSupply = totalSupply(); + mathint balanceOfAddr1 = balanceOf(addr1); + mathint balanceOfAddr2 = balanceOf(addr2); + mathint balanceOfAddr3 = balanceOf(addr3); + mathint balanceOfAddr4 = balanceOf(addr4); + + //Case all different + require addr1 != addr2 && addr1 != addr3 && addr1 != addr4 && addr2 != addr3 && addr2 != addr4 && addr3 != addr4 => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3 + balanceOfAddr4; + + //Case two are equal + require (addr1 == addr2 && addr1 != addr3 && addr1 != addr4 && addr3 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr3 + balanceOfAddr4; + require (addr1 == addr3 && addr1 != addr2 && addr1 != addr4 && addr2 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; + require (addr1 == addr4 && addr1 != addr2 && addr1 != addr3 && addr2 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + require (addr2 == addr3 && addr2 != addr1 && addr2 != addr4 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; + require (addr2 == addr4 && addr2 != addr1 && addr2 != addr3 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + require (addr3 == addr4 && addr3 != addr1 && addr3 != addr2 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + + //Cases two are equal and the other two as well. + require (addr1 == addr2 && addr3 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr3; + require (addr1 == addr3 && addr2 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2; + require (addr2 == addr3 && addr1 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2; + + //Cases three are same + require (addr1 == addr2 && addr2 == addr3 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr4; //4 differs + require (addr1 == addr2 && addr2 == addr4 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr3; //3 differs + require (addr1 == addr3 && addr3 == addr4 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2; //2 differs + require (addr2 == addr3 && addr3 == addr4 && addr1 != addr2) => totalSupply == balanceOfAddr2 + balanceOfAddr1; //1 differs + + require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; +} + +function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address addr3, address addr4){ + + mathint totalSupply = __ERC20.totalSupply(); + + if(addr1 != currentContract && addr2 != currentContract  && addr3 != currentContract && addr4 != currentContract){ + totalSupply = totalSupply - __ERC20.balanceOf(currentContract); + } + mathint balanceOfAddr1 = __ERC20.balanceOf(addr1); + mathint balanceOfAddr2 = __ERC20.balanceOf(addr2); + mathint balanceOfAddr3 = __ERC20.balanceOf(addr3); + mathint balanceOfAddr4 = __ERC20.balanceOf(addr4); + + //Case all different + require addr1 != addr2 && addr1 != addr3 && addr1 != addr4 && addr2 != addr3 && addr2 != addr4 && addr3 != addr4 => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3 + balanceOfAddr4; + + //Case two are equal + require (addr1 == addr2 && addr1 != addr3 && addr1 != addr4 && addr3 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr3 + balanceOfAddr4; + require (addr1 == addr3 && addr1 != addr2 && addr1 != addr4 && addr2 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; + require (addr1 == addr4 && addr1 != addr2 && addr1 != addr3 && addr2 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + require (addr2 == addr3 && addr2 != addr1 && addr2 != addr4 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr4; + require (addr2 == addr4 && addr2 != addr1 && addr2 != addr3 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + require (addr3 == addr4 && addr3 != addr1 && addr3 != addr2 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2 + balanceOfAddr3; + + //Cases two are equal and the other two as well. + require (addr1 == addr2 && addr3 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr3; + require (addr1 == addr3 && addr2 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2; + require (addr2 == addr3 && addr1 == addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr2; + + //Cases three are same + require (addr1 == addr2 && addr2 == addr3 && addr1 != addr4) => totalSupply == balanceOfAddr1 + balanceOfAddr4; //4 differs + require (addr1 == addr2 && addr2 == addr4 && addr1 != addr3) => totalSupply == balanceOfAddr1 + balanceOfAddr3; //3 differs + require (addr1 == addr3 && addr3 == addr4 && addr1 != addr2) => totalSupply == balanceOfAddr1 + balanceOfAddr2; //2 differs + require (addr2 == addr3 && addr3 == addr4 && addr1 != addr2) => totalSupply == balanceOfAddr2 + balanceOfAddr1; //1 differs + + require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; +} + +rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + //"Activating" allowance case by removing this requires + //require(e.msg.sender == withdraw_owner); + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + + uint256 assets = mint(e, shares, mint_receiver); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} + + +rule inverseRedeemWithdrawInFavourForVault_ConcretizedValues(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); + //Further restrict arguments: Now every address is equal to e.msg.sender. + require(e.msg.sender == mint_receiver); + require(e.msg.sender == withdraw_receiver); + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + //TODO introduce a fifths person... != withdraw_owner + uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} + + + + +rule inverseRedeemWithdrawInFavourForVault_FullyAbstractNoAllowance(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); + //Further restrict arguments: Now every address is equal to e.msg.sender. + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + require(shares > 0); + + //TODO introduce a fifths person... != withdraw_owner + uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} + +rule inverseRedeemWithdrawInFavourForVault_ConcretizedShares(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); + //Further restrict arguments: Now every address is equal to e.msg.sender. + //require(e.msg.sender == mint_receiver); + // require(e.msg.sender == withdraw_receiver); + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + + + require(shares == 10000 || shares == 100000 || shares == 0xffff); + //TODO introduce a fifths person... != withdraw_owner + uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 4e8e76d..b90fa90 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -1,6 +1,6 @@ //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20Mock as __ERC20; +using ERC20 as __ERC20; @@ -182,6 +182,34 @@ rule inverseRedeemWithdrawInFavourForVault_ConcretizedValues(uint256 shares, add + +rule inverseRedeemWithdrawInFavourForVault_FullyAbstractNoAllowance(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ + env e; + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); + + //Dismiss allowance case + require(e.msg.sender == withdraw_owner); + //Further restrict arguments: Now every address is equal to e.msg.sender. + + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); + + require(e.msg.sender != currentContract); + require(e.msg.sender != __ERC20); + + require(shares > 0); + + //TODO introduce a fifths person... != withdraw_owner + uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; +} + rule inverseRedeemWithdrawInFavourForVault_ConcretizedShares(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); From 6f67c56aaa5624098fb306b68f0de227653579e7 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 11:12:06 +0200 Subject: [PATCH 089/125] Fixing spec for non open zeppelin --- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index b90fa90..00d432e 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -13,7 +13,7 @@ methods{ function totalSupply() external returns uint256 envfree; function previewWithdraw(uint256 assets) external returns uint256 envfree; function previewRedeem(uint256 shares) external returns uint256 envfree; - function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); + // function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); //function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); //function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); } From dd25af70bbba04ed95f946f29cfec6cdebd8755c Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 15:13:41 +0200 Subject: [PATCH 090/125] Loading values to displaying them in Certora variable view. --- .../certora/specs/ERC4626-RoundingProps.spec | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 00d432e..80d2d6e 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -144,11 +144,27 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); + require(withdraw_owner != __ERC20); + require(withdraw_receiver != __ERC20); + require(withdraw_owner != currentContract); + require(withdraw_receiver != currentContract); + //Loading values to displaying them in Certora variable view. + uint256 assetBalanceBefore = __ERC20.balanceOf(e.msg.sender); + uint256 sharesBalanceAfter = balanceOf(e.msg.sender); uint256 assets = mint(e, shares, mint_receiver); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + //Loading values to displaying them in Certora variable view. + uint256 assetBalanceIntermediate = __ERC20.balanceOf(e.msg.sender); + uint256 sharesBalanceIntermediate = balanceOf(e.msg.sender); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + //Loading values to displaying them in Certora variable view. + uint256 assetBalanceAfter = __ERC20.balanceOf(e.msg.sender); + uint256 sharesBalanceAfter = balanceOf(e.msg.sender); + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } From 95ff65b7ef8ff4fe0c85ac36f6caaf54dbdd2d9e Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 15:17:51 +0200 Subject: [PATCH 091/125] Loading variables for certora to display --- .../ERC4626-RoundingProps.spec | 17 +++++++++++++++++ .../certora/specs/ERC4626-RoundingProps.spec | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index aaebf29..e939896 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -144,10 +144,27 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); + require(withdraw_owner != __ERC20); + require(withdraw_receiver != __ERC20); + require(withdraw_owner != currentContract); + require(withdraw_receiver != currentContract); + //Loading values to displaying them in Certora variable view. + uint256 assetBalanceBefore = __ERC20.balanceOf(e.msg.sender); + uint256 sharesBalanceBefore = balanceOf(e.msg.sender); uint256 assets = mint(e, shares, mint_receiver); + + //Loading values to displaying them in Certora variable view. + uint256 assetBalanceIntermediate = __ERC20.balanceOf(e.msg.sender); + uint256 sharesBalanceIntermediate = balanceOf(e.msg.sender); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + //Loading values to displaying them in Certora variable view. + uint256 assetBalanceAfter = __ERC20.balanceOf(e.msg.sender); + uint256 sharesBalanceAfter = balanceOf(e.msg.sender); + assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; } diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 80d2d6e..f27de4f 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -151,7 +151,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address //Loading values to displaying them in Certora variable view. uint256 assetBalanceBefore = __ERC20.balanceOf(e.msg.sender); - uint256 sharesBalanceAfter = balanceOf(e.msg.sender); + uint256 sharesBalanceBefore = balanceOf(e.msg.sender); uint256 assets = mint(e, shares, mint_receiver); From f44ea66fc8aad685d2b25817730559b78afe8eb5 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 15:25:19 +0200 Subject: [PATCH 092/125] Refining rule to get better counter example --- .github/workflows/certora.yml | 4 ++-- .../src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 2 +- .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml index 2bccc49..d138a64 100644 --- a/.github/workflows/certora.yml +++ b/.github/workflows/certora.yml @@ -97,6 +97,6 @@ jobs: #- {name: transferSpec, command: 'ERC20.conf --rule transferSpec'} #- {name: generalRulesOnERC20, command: 'generalRules_ERC20.conf --debug'} #- {name: generalRulesOnVAULT, command: 'generalRules_VAULT.conf --debug'} - - {name: RulesForERC4626, command: 'src/certora/conf/certoraRun-RoundingProps.conf --debug'} - - {name: RulesForERC4626, command: 'src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf --debug'} + - {name: RulesForERC4626, command: 'src/certora/conf/certoraRun-RoundingProps.conf'} + - {name: RulesForERC4626, command: 'src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf'} diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index e939896..09fee6b 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -166,7 +166,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address uint256 sharesBalanceAfter = balanceOf(e.msg.sender); - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; + assert shares >= withdrawnShares && assetBalanceBefore >= assetBalanceAfter, "User cannot gain assets using deposit / redeem combination."; } diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index f27de4f..53cf8b6 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -165,7 +165,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address uint256 assetBalanceAfter = __ERC20.balanceOf(e.msg.sender); uint256 sharesBalanceAfter = balanceOf(e.msg.sender); - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; + assert shares >= withdrawnShares && assetBalanceBefore >= assetBalanceAfter, "User cannot gain assets using deposit / redeem combination."; } From 23a24cc6437bbeb685eb22203d1225be92961507 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 16:20:08 +0200 Subject: [PATCH 093/125] Limiting search space to get plausible counter example --- .../src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 2 ++ .../erc4626/src/certora/specs/ERC4626-RoundingProps.spec | 1 + lesson4_reading/erc4626/src/certora/specs/Properties.md | 2 ++ 3 files changed, 5 insertions(+) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 09fee6b..b147dba 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -149,6 +149,8 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(withdraw_owner != currentContract); require(withdraw_receiver != currentContract); + require(withdraw_receiver == e.msg.sender); + //Loading values to displaying them in Certora variable view. uint256 assetBalanceBefore = __ERC20.balanceOf(e.msg.sender); uint256 sharesBalanceBefore = balanceOf(e.msg.sender); diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 53cf8b6..5ece647 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -148,6 +148,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(withdraw_receiver != __ERC20); require(withdraw_owner != currentContract); require(withdraw_receiver != currentContract); + require(withdraw_receiver == e.msg.sender); //Loading values to displaying them in Certora variable view. uint256 assetBalanceBefore = __ERC20.balanceOf(e.msg.sender); diff --git a/lesson4_reading/erc4626/src/certora/specs/Properties.md b/lesson4_reading/erc4626/src/certora/specs/Properties.md index 8d37a61..7573d9d 100644 --- a/lesson4_reading/erc4626/src/certora/specs/Properties.md +++ b/lesson4_reading/erc4626/src/certora/specs/Properties.md @@ -105,6 +105,8 @@ Properties that may not be testable Other Properties ------ +* `withdraw` and `redeem` are semantically equivalent when the inputs are converted using the respective `convert` method. +* `mint` and `deposit` are semantically equivalent when the inputs are converted using the respective `convert` method. * Deposit increases the number of shares by mint. Shares minted should be proportional to the increase of the balance of the vault. ``` a: amount to deposit From eda4c65dff5fc2860f097cd75d0beb4965aaa53b Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 16:24:51 +0200 Subject: [PATCH 094/125] Limiting search space --- .../src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index b147dba..29e904c 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -150,6 +150,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(withdraw_receiver != currentContract); require(withdraw_receiver == e.msg.sender); + require(mint_receiver == e.msg.sender); //Loading values to displaying them in Certora variable view. uint256 assetBalanceBefore = __ERC20.balanceOf(e.msg.sender); From da34f00366133dc84d16bb6989d39378d7cf3f57 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Mon, 18 Sep 2023 16:33:48 +0200 Subject: [PATCH 095/125] Adding missing requires --- .../src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 29e904c..112a8bf 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -149,6 +149,7 @@ rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address require(withdraw_owner != currentContract); require(withdraw_receiver != currentContract); + require(withdraw_owner == e.msg.sender); require(withdraw_receiver == e.msg.sender); require(mint_receiver == e.msg.sender); From 21d06766147553bb1df88369b35249c6d11416f6 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 10:30:26 +0200 Subject: [PATCH 096/125] Duplicate rules for OpenZeppelin and base implementation --- .../certoraRun-InflationAttack.conf | 4 +- .../ERC4626-FunctionalAccountingProps.spec | 131 +++++++++++++ .../ERC4626-InflationAttack.spec | 164 ++++++++++++++++ .../ERC4626-MonotonicityInvariant.spec | 181 ++++++++++++++++++ .../ERC4626-MustNotRevertProps.spec | 83 ++++++++ .../ERC4626-RedeemUsingApprovalProps.spec | 118 ++++++++++++ .../ERC4626-SecurityProps.spec | 76 ++++++++ .../ERC4626-FunctionalAccountingProps.spec | 2 +- .../specs/ERC4626-InflationAttack.spec | 4 +- .../specs/ERC4626-MonotonicityInvariant.spec | 45 +---- .../specs/ERC4626-MustNotRevertProps.spec | 2 +- .../certora/specs/ERC4626-SecurityProps.spec | 2 +- .../token/ERC20/extensions/ERC4626.sol | 2 +- 13 files changed, 767 insertions(+), 47 deletions(-) create mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-FunctionalAccountingProps.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MustNotRevertProps.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RedeemUsingApprovalProps.spec create mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-SecurityProps.spec diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf index 0ef6e4b..b7f8a12 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf @@ -3,13 +3,13 @@ "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], - "verify": "ERC4626Mock:src/certora/specs/ERC4626-InflationAttack.spec", + "verify": "ERC4626Mock:src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec", "link": ["ERC4626Mock:_asset=ERC20Mock"], "solc": "solc8.20", "prover_version": "master", "server": "production", "rule_sanity": "basic", "send_only": true, - "msg": "Verification of ERC4626", + "msg": "Verification of ERC4626 OpenZeppelin", "optimistic_loop": true } \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-FunctionalAccountingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-FunctionalAccountingProps.spec new file mode 100644 index 0000000..ea13e96 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-FunctionalAccountingProps.spec @@ -0,0 +1,131 @@ +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to __ERC20 as of import that already declares _ERC20. +using ERC20Mock as __ERC20; + +//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; +use invariant mirrorIsCorrectERC20; +use invariant mirrorIsCorrectERC4626; + +methods { + function __ERC20.balanceOf(address) external returns uint256 envfree; + + function balanceOf(address) external returns uint256 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function totalSupply() external returns uint256 envfree; +} + +rule depositProperties(uint256 assets, address receiver, address owner){ + safeAssumptions(); + env e; + //Not an assumption, but just creating an alias for e.msg.sender. Tryout if uint256 owver = e.msg.sender is a better choice here? + require(e.msg.sender == owner); + + //The caller may not be the currentContract. Is that a fair assumption? + //Not sure if solidity allows a way to to fake e.msg.sender attribute. (Delegate call, reflection,...?) + require(owner != currentContract); + + + mathint ownerAssetsBefore = __ERC20.balanceOf(owner); + mathint receiverSharesBefore = balanceOf(receiver); + + mathint previewShares = previewDeposit(assets); + mathint shares = deposit(e, assets, receiver); + + mathint ownerAssetsAfter = __ERC20.balanceOf(owner); + mathint receiverSharesAfter = balanceOf(receiver); + + assert ownerAssetsAfter + assets == ownerAssetsBefore; + assert receiverSharesAfter - shares == receiverSharesBefore; + assert shares >= previewShares; +} + +rule mintProperties(uint256 shares, address receiver, address owner){ + safeAssumptions(); + env e; + require(e.msg.sender == owner); + + //The caller may not be the currentContract. Is that a fair assumption? + //Not sure if solidity allows a way to to fake e.msg.sender attribute. (Delegate call, reflection,...?) + require(owner != currentContract); + + + mathint ownerAssetsBefore = __ERC20.balanceOf(owner); + mathint receiverSharesBefore = balanceOf(receiver); + + mathint previewAssets = previewMint(shares); + mathint assets = mint(e, shares, receiver); + + + mathint ownerAssetsAfter = __ERC20.balanceOf(owner); + mathint receiverSharesAfter = balanceOf(receiver); + + assert ownerAssetsAfter + assets == ownerAssetsBefore; + assert receiverSharesAfter - shares == receiverSharesBefore; + assert assets <= previewAssets; +} + + + +rule withdrawProperties(uint256 assets, address receiver, address owner){ + safeAssumptions(); + env e; + + //The caller may not be the currentContract. Is that a fair assumption? + //Not sure if solidity allows a way to to fake e.msg.sender attribute. (Delegate call, reflection,...?) + require(e.msg.sender != currentContract); + + mathint ownerSharesBefore = balanceOf(owner); + mathint receiverAssetsBefore = __ERC20.balanceOf(receiver); + + mathint previewShares = previewWithdraw(assets); + mathint shares = withdraw(e, assets, receiver, owner); + + + mathint ownerSharesAfter = balanceOf(owner); + mathint receiverAssetsAfter = __ERC20.balanceOf(receiver); + + assert ownerSharesAfter + shares == ownerSharesBefore; + assert receiver != currentContract => receiverAssetsAfter - assets == receiverAssetsBefore; + + //Is this according to specifications or a bug? Couldn't find a clear answer to it. Probably yes, receiverAssets remain unchanged, at least don't increase. + assert receiver == currentContract => receiverAssetsAfter == receiverAssetsBefore; + + assert shares <= previewShares; +} + + +rule redeemProperties(uint256 shares, address receiver, address owner){ + safeAssumptions(); + env e; + + //The caller may not be the currentContract. Is that a fair assumption? + //Not sure if solidity allows a way to to fake e.msg.sender attribute. (Delegate call, reflection,...?) + require(e.msg.sender != currentContract); + + mathint ownerSharesBefore = balanceOf(owner); + mathint receiverAssetsBefore = __ERC20.balanceOf(receiver); + + mathint previewAssets = previewRedeem(shares); + mathint assets = redeem(e, shares, receiver, owner); + + + mathint ownerSharesAfter = balanceOf(owner); + mathint receiverAssetsAfter = __ERC20.balanceOf(receiver); + + assert ownerSharesAfter + shares == ownerSharesBefore; + assert receiver != currentContract => receiverAssetsAfter - assets == receiverAssetsBefore; + + //Is this according to specifications or a bug? Couldn't find a clear answer to it. Probably yes, receiverAssets remain unchanged, at least don't increase. + assert receiver == currentContract => receiverAssetsAfter == receiverAssetsBefore; + + assert assets >= previewAssets; +} diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec new file mode 100644 index 0000000..32b1fff --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec @@ -0,0 +1,164 @@ + +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. +using ERC20Mock as __ERC20; + +//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; +use invariant mirrorIsCorrectERC20; +use invariant mirrorIsCorrectERC4626; + + +methods{ + function __ERC20.allowance(address,address) external returns uint256 envfree; + function __ERC20.balanceOf(address) external returns uint256 envfree; + function __ERC20.decimals() external returns uint8 envfree; + function __ERC20.totalSupply() external returns uint256 envfree; + + function balanceOf(address) external returns uint256 envfree; + function convertToAssets(uint256) external returns uint256 envfree; + function convertToShares(uint256) external returns uint256 envfree; + function decimals() external returns uint8 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function totalSupply() external returns uint256 envfree; +} + + + +rule simpleVersionOfVulnerableAttack(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_ownver) { + env e; + safeAssumptions(); + address attacker = e.msg.sender; + + require(balanceOf(attacker) == 0); + require(balanceOf(deposit_receiver) == 0); + require(balanceOf(redeem_receiver) == 0); + require(balanceOf(redeem_ownver) == 0); + + require(attacker != currentContract); + + uint256 shares = deposit(e, assets, deposit_receiver); + + //In the inflationAttack there are 2 steps that we don't model here! + + uint256 receivedAssets = redeem(e, shares, redeem_receiver, redeem_ownver); + assert(receivedAssets <= assets); +} + + + + +//Source: Medium Article by Shao https://tienshaoku.medium.com/eip-4626-inflation-sandwich-attack-deep-dive-and-how-to-solve-it-9e3e320cc3f1 +rule vulnerableToInflationAttack(address attacker, address victim, address deposit1_receiver, address deposit2_victim_receiver,address redeem_receiver,address redeem_ownver ){ + + //Doesn't work properly...Retry later. + /*requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20;*/ + //Doesn't work + //require forall address x. balanceOf(x) <= totalSupply(); + //Doesn't work + //require forall address y. __ERC20.balanceOf(y) <= __ERC20.totalSupply(); + safeAssumptions(); + uint256 oneEther; + uint256 oneWei; + + require(oneWei > 0); + require(oneEther > 0); + + mathint assetsAttackerPreAttack = to_mathint(oneEther) + to_mathint(oneWei); + uint8 ERC4626decimals = decimals(); + uint8 ERC20decimals = __ERC20.decimals(); + + require(attacker != currentContract); + require(attacker != __ERC20); + require(attacker != 0); + require(victim != currentContract); + require(victim != __ERC20); + require(victim != 0); + require(victim != attacker); + + //Following the pattern "First Deposit" of the article. + require(totalSupply() == 0); + require(totalAssets() == 0); + + //Duplicated all requireInvariants + //Doesn't work either.... + require(balanceOf(attacker) == 0); + require(balanceOf(victim) == 0); + require(balanceOf(deposit1_receiver) == 0); + require(balanceOf(deposit2_victim_receiver) == 0); + require(balanceOf(redeem_receiver) == 0); + require(balanceOf(redeem_ownver) == 0); + + uint256 before_step_1_totalSupply = totalSupply(); + uint256 before_step_1_totalAssets = totalAssets(); + + /** + * Step 1: the attacker front-runs the depositor and deposits 1 wei WETH and gets 1 share: since totalSupply is 0, shares = 1 * 10**18 / 10**18 = 1 + */ + env e1; + require(e1.msg.sender == attacker); + uint256 firstShares = deposit(e1, oneEther, deposit1_receiver); + + uint256 before_step_2_totalSupply = totalSupply(); + uint256 before_step_2_totalAssets = totalAssets(); + + env e2; + require(e2.msg.sender == attacker); + require(e2.block.timestamp > e1.block.timestamp); + + require(__ERC20.balanceOf(attacker) >= oneWei); + + /** + * Step 2: the attacker also transfers 1 * 1e18 weiWETH, making the totalAssets() WETH balance of the vault become 1e18 + 1 wei + */ + __ERC20.transferFrom(e2, attacker, currentContract, oneWei); + require(__ERC20.balanceOf(currentContract) > 0); + + uint256 before_step_3_totalSupply = totalSupply(); + uint256 before_step_3_totalAssets = totalAssets(); + + //assert before_step_3_totalSupply > 0; + + + /** + * Step 3: + * The spied-on depositor deposits 1e18 wei WETH. However, the depositor gets 0 shares: 1e18 * 1 (totalSupply) / (1e18 + 1) = 1e18 / (1e18 + 1) = 0. + * Since the depositor gets 0 shares, totalSupply() remains at 1 + */ + env e3; + require(e3.msg.sender == victim); + require(e3.block.timestamp > e2.block.timestamp); + uint256 previweAssets = previewDeposit(oneWei); + uint256 victimShares = deposit(e3, oneWei, deposit2_victim_receiver); + + /** + * Step 4: the attacker still has the 1 only share ever minted and thus the withdrawal of + * that 1 share takes away everything in the vault, including the depositor’s 1e18 weiWETH + */ + + uint256 before_step_4_totalSupply = totalSupply(); + uint256 before_step_4_totalAssets = totalAssets(); + uint256 random; + env e4; + require(e4.msg.sender == attacker); + require(e4.block.timestamp > e3.block.timestamp); + //TODO: can attacker actually withdraw `convertToAssets(before_step_4_totalSupply)` or only `assetsAttackerPreAttack` + mathint assetsAttackerPostAttack = redeem(e4, before_step_4_totalSupply, redeem_receiver, redeem_ownver); + + uint256 finalTotalAssets = totalAssets(); + uint256 finalTotalSupply = totalSupply(); + mathint assetsAttackerGained = assetsAttackerPostAttack - assetsAttackerPreAttack; + + assert assetsAttackerPreAttack >= assetsAttackerPostAttack, "The attacker gained assets."; +} diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec new file mode 100644 index 0000000..1427886 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec @@ -0,0 +1,181 @@ + +using ERC20Mock as _ERC20; + +methods { + function totalSupply() external returns uint256 envfree; + function balanceOf(address) external returns uint256 envfree; + function allowance(address, address) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + function _ERC20.totalSupply() external returns uint256 envfree; + function _ERC20.balanceOf(address) external returns uint256 envfree; + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); + //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); + //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); +} + + +function safeAssumptions(){ + requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; +} + +function balaceMirrorsAreCorrect(address x) { + requireInvariant mirrorIsCorrectERC20(x); + requireInvariant mirrorIsCorrectERC4626(x); +} + +function safeAssumptionsERC20() { + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; +} + +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { + uint256 res; + require(res * denominator) <= x * y; + require(res * denominator) > x * y - 1; + return res; +} + +//TODO: Careful here: we have ERC462 and ERC20 balance and totalSupply... +function updateSafe(address from, address to, uint256 amount){ + uint256 balanceOfTo = balanceOf(to); + uint256 balanceOfFrom = balanceOf(from); + uint256 totalSupply = totalSupply(); + + + //transfer or mint case + if(to != 0 && from != to){ + require(balanceOfTo >= amount); + } + //transfer or burn case + if(from != 0 && from != to){ + require(balanceOfFrom >= amount); + } + //mint case + if(from == 0 && to != 0){ + require(totalSupply >= amount); + } + //burn case + if(from != 0 && to == 0){ + require(totalSupply >= amount); + } +} + +rule assetAndShareMonotonicy(){ + + safeAssumptions(); + uint256 totalAssetsBefore = totalAssets(); + uint256 totalSupplyBefore = totalSupply(); + + method f; + env e; + uint256 amount; + address receiver; + address owner; + if(f.selector == sig:mint(uint,address).selector){ + mint(e, amount, receiver); + } else if(f.selector == sig:withdraw(uint,address,address).selector){ + withdraw(e, amount, receiver, owner); + } else if(f.selector == sig:deposit(uint,address).selector){ + deposit(e, amount, receiver); + } else if(f.selector == sig:redeem(uint,address,address).selector){ + redeem(e, amount, receiver, owner); + } else { + calldataarg args; + f(e,args); + } + + uint256 totalAssetsAfter = totalAssets(); + uint256 totalSupplyAfter = totalSupply(); + + require(e.msg.sender != currentContract); + assert totalSupplyBefore < totalSupplyAfter <=> totalAssetsBefore < totalAssetsAfter , "Strong monotonicity doesn't hold."; + assert (receiver != currentContract) => (totalAssetsBefore <= totalAssetsAfter <=> totalSupplyBefore <= totalSupplyAfter), "Monotonicity doesn't hold."; +} + +/** +* This invariant does not hold for OpenZeppelin. There is a public function mint that allows to increase totalSupply without increasing totalAssets! +*/ +invariant totalAssetsZeroImpliesTotalSupplyZero() + totalAssets() == 0 => totalSupply() == 0 + { + + preserved { + requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; + } +} + +invariant sumOfBalancesEqualsTotalSupplyERC4626() + sumOfBalancesERC4626 == to_mathint(totalSupply()); + +ghost mathint sumOfBalancesERC4626 { + init_state axiom sumOfBalancesERC4626 == 0; +} + +hook Sstore ERC4626Mock._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalancesERC4626 = sumOfBalancesERC4626 + newValue - oldValue; + userBalanceERC4626 = newValue; + balanceOfMirroredERC4626[user] = newValue; +} +hook Sload uint256 value ERC4626Mock._balances[KEY address user] STORAGE { + //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to hold. + require to_mathint(value) <= sumOfBalancesERC4626; + require value == balanceOfMirroredERC4626[user]; +} + +invariant sumOfBalancesEqualsTotalSupplyERC20() + sumOfBalancesERC20 == to_mathint(_ERC20.totalSupply()); + +ghost mathint sumOfBalancesERC20 { + init_state axiom sumOfBalancesERC20 == 0; +} + +hook Sstore _ERC20._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; + userBalanceERC20 = newValue; + balanceOfMirroredERC20[user] = newValue; +} + +hook Sload uint256 value _ERC20._balances[KEY address user] STORAGE { + //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to already hold. + require to_mathint(value) <= sumOfBalancesERC20; + require value == balanceOfMirroredERC20[user]; +} + +invariant singleUserBalanceSmallerThanTotalSupplyERC20() + userBalanceERC20 <= sumOfBalancesERC20; + +ghost mathint userBalanceERC20 { + init_state axiom userBalanceERC20 == 0; +} + +invariant singleUserBalanceSmallerThanTotalSupplyERC4626() + userBalanceERC4626 <= sumOfBalancesERC4626; + +ghost mathint userBalanceERC4626 { + init_state axiom userBalanceERC4626 == 0; +} + +ghost mapping(address => uint256) balanceOfMirroredERC4626 { + init_state axiom forall address a. (balanceOfMirroredERC4626[a] == 0); +} + +ghost mapping(address => uint256) balanceOfMirroredERC20 { + init_state axiom forall address a. (balanceOfMirroredERC20[a] == 0); +} + +invariant mirrorIsCorrectERC20(address x) + balanceOfMirroredERC20[x] == _ERC20.balanceOf(x); + + +invariant mirrorIsCorrectERC4626(address x) + balanceOfMirroredERC4626[x] == balanceOf(x); \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MustNotRevertProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MustNotRevertProps.spec new file mode 100644 index 0000000..cf051d9 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MustNotRevertProps.spec @@ -0,0 +1,83 @@ + + +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. +using ERC20Mock as __ERC20; + +//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; + + + +methods{ + function balanceOf(address) external returns uint256 envfree; + function convertToAssets(uint256) external returns uint256 envfree; + function convertToShares(uint256) external returns uint256 envfree; + function maxDeposit(address) external returns uint256 envfree; + function maxMint(address) external returns uint256 envfree; + function maxRedeem(address) external returns uint256 envfree; + function maxWithdraw(address) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function totalSupply() external returns uint256 envfree; +} + + +definition nonReveritngFunction(method f) returns bool = + f.selector == sig:convertToAssets(uint256).selector + || f.selector == sig:convertToShares(uint256).selector + || f.selector == sig:totalAssets().selector + || f.selector == sig:asset().selector + || f.selector == sig:maxDeposit(address).selector + || f.selector == sig:maxMint(address).selector + || f.selector == sig:maxRedeem(address).selector + || f.selector == sig:maxWithdraw(address).selector; + + +rule mustNotRevertProps(method f) filtered {f -> nonReveritngFunction(f)}{ + env e; + require(e.msg.value == 0); + safeAssumptions(); + bool res = callMethodsWithParamenter(e, f); + assert res == false, "Method ${f} reverted."; +} + +function callMethodsWithParamenter(env e, method f) returns bool { + uint256 amount; + address addr; + if(f.selector == sig:convertToAssets(uint256).selector){ + /**OZ: + We'd need to ensure that totalAssets() + 1 does not overflow....totalSupply() + 10**__decimalsOffset() doesn't overflow and the mulDiv doesn't either + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + } + */ + convertToAssets@withrevert(amount); + return lastReverted; + } else if(f.selector == sig:convertToShares(uint256).selector){ + convertToShares@withrevert(amount); + return lastReverted; + } else if(f.selector == sig:maxWithdraw(address).selector){ + maxWithdraw@withrevert(addr); + return lastReverted; + } else if(f.selector == sig:maxDeposit(address).selector){ + maxDeposit@withrevert(addr); + return lastReverted; + } else if(f.selector == sig:maxMint(address).selector){ + maxMint@withrevert(addr); + return lastReverted; + } else if(f.selector == sig:maxRedeem(address).selector){ + maxRedeem@withrevert(addr); + return lastReverted; + } else if(f.selector == sig:totalAssets().selector || f.selector == sig:asset().selector){ + calldataarg args; + f@withrevert(e, args); + return lastReverted; + } + //Should be unreachable. + return true; +} diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RedeemUsingApprovalProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RedeemUsingApprovalProps.spec new file mode 100644 index 0000000..fecdb3c --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RedeemUsingApprovalProps.spec @@ -0,0 +1,118 @@ +using ERC20Mock as _ERC20; + +methods { + function _ERC20.balanceOf(address) external returns uint256 envfree; + function allowance(address, address) external returns uint256 envfree; + function balanceOf(address) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; +} + +/** +* Special case for when e.msg.sender == owner. +* 1. Third party `withdraw()` calls must update the msg.sender's allowance +* 2. withdraw() must allow proxies to withdraw tokens on behalf of the owner using share token approvals +* 3. Check that is doesn't revert. +*/ +rule ownerWithdrawal(uint256 assets, address receiver, address owner){ + env e; + require(e.msg.sender == owner); + + uint256 allowanceBefore = allowance(owner, e.msg.sender); + withdraw@withrevert(e, assets, receiver, owner); + uint256 allowanceAfter = allowance(owner, e.msg.sender); + assert allowanceAfter == allowanceBefore; + assert lastReverted == false; +} + + +//Third party `withdraw()` calls must update the msg.sender's allowance +//withdraw() must allow proxies to withdraw tokens on behalf of the owner using share token approvals +rule thirdPartyWithdrawal(uint256 assets, address receiver, address owner){ + env e; + require(e.msg.sender != owner); + + uint256 allowanceBefore = allowance(owner, e.msg.sender); + uint256 shares = previewWithdraw(assets); + + withdraw(e, assets, receiver, owner); + + uint256 allowanceAfter = allowance(owner, e.msg.sender); + assert allowanceAfter <= allowanceBefore; + assert shares <= allowanceBefore; +} + +//Third parties must not be able to withdraw() tokens on an owner's behalf without a token approval +rule thirdPartyWithdrawalRevertCase(uint256 assets, address receiver, address owner){ + env e; + uint256 allowanceBefore = allowance(owner, e.msg.sender); + uint256 shares = previewWithdraw(assets); + + require shares > allowanceBefore; + //If e.msg.sender is the owner, no allowance is required, see rule ownerWithdrawal + require e.msg.sender != owner; + + withdraw@withrevert(e, assets, receiver, owner); + + bool withdrawReverted = lastReverted; + + assert withdrawReverted, "withdraw does not revert when no allowance provided."; +} + + + +/** +* Special case for when e.msg.sender == owner. +* 1. Third party `redeem()` calls must update the msg.sender's allowance +* 2. redeem() must allow proxies to redeem shares on behalf of the owner using share token approvals +* 3. Check that is doesn't revert. +*/ +rule ownerRedeem(uint256 shares, address receiver, address owner){ + env e; + require(e.msg.sender == owner); + + uint256 allowanceBefore = allowance(owner, e.msg.sender); + redeem@withrevert(e, shares, receiver, owner); + uint256 allowanceAfter = allowance(owner, e.msg.sender); + assert allowanceAfter == allowanceBefore; + assert lastReverted == false; +} + + +//Third party `redeem()` calls must update the msg.sender's allowance +//redeem() must allow proxies to withdraw tokens on behalf of the owner using share token approvals +rule thirdPartyRedeem(uint256 shares, address receiver, address owner){ + env e; + require(e.msg.sender != owner); + + uint256 allowanceBefore = allowance(owner, e.msg.sender); + uint256 assets = previewRedeem(shares); + + redeem(e, shares, receiver, owner); + + uint256 allowanceAfter = allowance(owner, e.msg.sender); + assert allowanceAfter <= allowanceBefore; + assert shares <= allowanceBefore; +} + +//Third parties must not be able to redeem() tokens on an owner's behalf without a token approval +rule thirdPartyRedeemRevertCase(uint256 shares, address receiver, address owner){ + env e; + uint256 allowanceBefore = allowance(owner, e.msg.sender); + uint256 assets = previewRedeem(shares); + + require shares > allowanceBefore; + //If e.msg.sender is the owner, no allowance is required, see rule ownerWithdrawal + require e.msg.sender != owner; + + redeem@withrevert(e, shares, receiver, owner); + + bool redeemReverted = lastReverted; + + assert redeemReverted, "redeem does not revert when no allowance provided."; +} + + +invariant balanceOfERC20EqualToTotalAsset() + totalAssets() == _ERC20.balanceOf(currentContract); diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-SecurityProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-SecurityProps.spec new file mode 100644 index 0000000..6ea85a5 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-SecurityProps.spec @@ -0,0 +1,76 @@ +import "./ERC4626-MonotonicityInvariant.spec"; + +//Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. +using ERC20Mock as __ERC20; + +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; + + +methods { + function allowance(address, address) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function decimals() external returns uint8 envfree; + function __ERC20.decimals() external returns uint8 envfree; + function totalSupply() external returns uint256 envfree; +} + + +//deposit must increase totalSupply +rule depositMustIncreaseTotalSupply(uint256 assets, address user){ + safeAssumptions(); + + uint256 totalSupplyBefore = totalSupply(); + env e; + deposit(e, assets, user); + uint256 totalSupplyAfter = totalSupply(); + assert totalSupplyAfter >= totalSupplyBefore, "Total supply must increase when deposit is called."; +} + +//mint must increase totalAssets +rule mintMustIncreaseTotalAssets(uint256 shares, address user){ + safeAssumptions(); + + uint256 totalAssetsBefore = totalAssets(); + env e; + mint(e, shares, user); + uint256 totalAssetsAfter = totalAssets(); + assert totalAssetsAfter >= totalAssetsBefore, "Total assets must increase when mint is called."; +} + +//withdraw must decrease totalAssets +rule withdrawMustDecreaseTotalSupply(uint256 assets, address receiver, address owner){ + safeAssumptions(); + + uint256 totalSupplyBefore = totalSupply(); + env e; + + withdraw(e, assets, receiver, owner); + uint256 totalSupplyAfter = totalSupply(); + assert totalSupplyAfter <= totalSupplyBefore, "Total supply must decrease when withdraw is called."; +} + +//redeem must decrease totalAssets +rule redeemMustDecreaseTotalAssets(uint256 shares, address receiver, address owner){ + safeAssumptions(); + + uint256 totalAssetsBefore = totalAssets(); + env e; + redeem(e, shares, receiver, owner); + uint256 totalAssetsAfter = totalAssets(); + assert totalAssetsAfter <= totalAssetsBefore, "Total assets must decrease when redeem is called."; +} + +//`decimals()` should be larger than or equal to `asset.decimals()` +rule decimalsOfUnderlyingVaultShouldBeLarger(uint256 shares, address receiver, address owner){ + //TODO: Rule fails. The method call to decimals returns a HAVOC'd value. Still the solver should be able to reason that ERC4626.decimals == ERC20.decimals as of the call to the super constructor. Don't understand why. + safeAssumptions(); + + uint8 assetDecimals = __ERC20.decimals(); + uint8 decimals = decimals(); + + assert decimals >= assetDecimals, "Decimals of underlying ERC20 should be larger than ERC4626 decimals."; +} diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec index ea13e96..2fcc3e4 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-FunctionalAccountingProps.spec @@ -1,7 +1,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to __ERC20 as of import that already declares _ERC20. -using ERC20Mock as __ERC20; +using ERC20 as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec index f985c52..a9f14b8 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec @@ -2,7 +2,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20Mock as __ERC20; +using ERC20 as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; @@ -10,6 +10,8 @@ use invariant sumOfBalancesEqualsTotalSupplyERC4626; use invariant sumOfBalancesEqualsTotalSupplyERC20; use invariant singleUserBalanceSmallerThanTotalSupplyERC20; use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; +use invariant mirrorIsCorrectERC20; +use invariant mirrorIsCorrectERC4626; methods{ diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec index 1427886..40db33a 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MonotonicityInvariant.spec @@ -1,5 +1,5 @@ -using ERC20Mock as _ERC20; +using ERC20 as _ERC20; methods { function totalSupply() external returns uint256 envfree; @@ -12,9 +12,6 @@ methods { function previewRedeem(uint256) external returns uint256 envfree; function _ERC20.totalSupply() external returns uint256 envfree; function _ERC20.balanceOf(address) external returns uint256 envfree; - function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); - //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); } @@ -35,38 +32,6 @@ function safeAssumptionsERC20() { requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; } -function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; - require(res * denominator) <= x * y; - require(res * denominator) > x * y - 1; - return res; -} - -//TODO: Careful here: we have ERC462 and ERC20 balance and totalSupply... -function updateSafe(address from, address to, uint256 amount){ - uint256 balanceOfTo = balanceOf(to); - uint256 balanceOfFrom = balanceOf(from); - uint256 totalSupply = totalSupply(); - - - //transfer or mint case - if(to != 0 && from != to){ - require(balanceOfTo >= amount); - } - //transfer or burn case - if(from != 0 && from != to){ - require(balanceOfFrom >= amount); - } - //mint case - if(from == 0 && to != 0){ - require(totalSupply >= amount); - } - //burn case - if(from != 0 && to == 0){ - require(totalSupply >= amount); - } -} - rule assetAndShareMonotonicy(){ safeAssumptions(); @@ -121,12 +86,12 @@ ghost mathint sumOfBalancesERC4626 { init_state axiom sumOfBalancesERC4626 == 0; } -hook Sstore ERC4626Mock._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { +hook Sstore currentContract.balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { sumOfBalancesERC4626 = sumOfBalancesERC4626 + newValue - oldValue; userBalanceERC4626 = newValue; balanceOfMirroredERC4626[user] = newValue; } -hook Sload uint256 value ERC4626Mock._balances[KEY address user] STORAGE { +hook Sload uint256 value currentContract.balanceOf[KEY address user] STORAGE { //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to hold. require to_mathint(value) <= sumOfBalancesERC4626; require value == balanceOfMirroredERC4626[user]; @@ -139,13 +104,13 @@ ghost mathint sumOfBalancesERC20 { init_state axiom sumOfBalancesERC20 == 0; } -hook Sstore _ERC20._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { +hook Sstore _ERC20.balanceOf[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; userBalanceERC20 = newValue; balanceOfMirroredERC20[user] = newValue; } -hook Sload uint256 value _ERC20._balances[KEY address user] STORAGE { +hook Sload uint256 value _ERC20.balanceOf[KEY address user] STORAGE { //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to already hold. require to_mathint(value) <= sumOfBalancesERC20; require value == balanceOfMirroredERC20[user]; diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec index cf051d9..59940cc 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-MustNotRevertProps.spec @@ -3,7 +3,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20Mock as __ERC20; +using ERC20 as __ERC20; //This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? use invariant totalAssetsZeroImpliesTotalSupplyZero; diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec index 6ea85a5..da9d6ac 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-SecurityProps.spec @@ -1,7 +1,7 @@ import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. -using ERC20Mock as __ERC20; +using ERC20 as __ERC20; use invariant totalAssetsZeroImpliesTotalSupplyZero; use invariant sumOfBalancesEqualsTotalSupplyERC4626; diff --git a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol index 3ac69a1..97d1703 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol @@ -275,7 +275,7 @@ contract ERC4626 is ERC20, IERC4626 { // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the // shares are burned and after the assets are transferred, which is a valid state. _burn(owner, shares); - _asset.transfer(receiver, assets); + SafeERC20.safeTransfer(_asset, receiver, assets); emit Withdraw(caller, receiver, owner, assets, shares); } From b68fa0f8b3f7f192a0631c86ef5d56765764ee8c Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 10:47:45 +0200 Subject: [PATCH 097/125] Starting point of rule writing of Rounding Props --- .../ERC4626-RoundingProps.spec | 198 +++++------------- .../certora/specs/ERC4626-RoundingProps.spec | 194 +++++------------ 2 files changed, 104 insertions(+), 288 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 112a8bf..592ff3f 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -1,8 +1,15 @@ +import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. using ERC20Mock as __ERC20; - - +//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; +use invariant mirrorIsCorrectERC20; +use invariant mirrorIsCorrectERC4626; methods{ function balanceOf(address) external returns uint256 envfree; @@ -13,49 +20,6 @@ methods{ function totalSupply() external returns uint256 envfree; function previewWithdraw(uint256 assets) external returns uint256 envfree; function previewRedeem(uint256 shares) external returns uint256 envfree; - function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - //function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); - //function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); -} -function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; -/* // require(res * denominator) <= x * y; -// require((res + 1) * denominator) > x * y; -*/ - - require(res * denominator) <= x * y; - require((res + 1) * denominator) >= x * y; - require x <= denominator; - require res <= y; - require x == 0 => res == 0; - require denominator > 0; - return res; - //return require_uint256(x * y / denominator); -} - - -//The ghost is supposed to cover the equation: assets >= converToAssets(convertToShares(assets)). -ghost uint256 lastCallConvertToShares_AssetsParameter{ - init_state axiom lastCallConvertToShares_AssetsParameter == 0; -} -//The ghost is supposed to cover the equation: shares >= convertToShares(convertToAssets(shares)) -ghost uint256 lastCallConvertToAssets_SharesParameter{ - init_state axiom lastCallConvertToAssets_SharesParameter == 0; -} - -function previewWithdrawSummary(uint256 assets) returns uint256 { - lastCallConvertToShares_AssetsParameter = assets; - uint256 convertedShares = previewWithdraw(assets); - require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); - return convertedShares; -} - -function previewRedeemSummary(uint256 shares) returns uint256 { - lastCallConvertToAssets_SharesParameter = shares; - uint256 convertedAssets = previewRedeem(shares); - - require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); - return convertedAssets; } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ @@ -127,136 +91,78 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; } -rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseDepositRedeemInFavourForVault(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_owner){ env e; - assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - - //Dismiss allowance case - //"Activating" allowance case by removing this requires - //require(e.msg.sender == withdraw_owner); - - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); - - require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); - require(withdraw_owner != __ERC20); - require(withdraw_receiver != __ERC20); - require(withdraw_owner != currentContract); - require(withdraw_receiver != currentContract); - - require(withdraw_owner == e.msg.sender); - require(withdraw_receiver == e.msg.sender); - require(mint_receiver == e.msg.sender); + safeAssumptions(); - //Loading values to displaying them in Certora variable view. - uint256 assetBalanceBefore = __ERC20.balanceOf(e.msg.sender); - uint256 sharesBalanceBefore = balanceOf(e.msg.sender); + assumeBalanceEqualSumManualERC20_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); - uint256 assets = mint(e, shares, mint_receiver); - - //Loading values to displaying them in Certora variable view. - uint256 assetBalanceIntermediate = __ERC20.balanceOf(e.msg.sender); - uint256 sharesBalanceIntermediate = balanceOf(e.msg.sender); + uint256 shares = deposit(e, assets, deposit_receiver); + uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + assert assets >= redeemedAssets, "User cannot gain assets using deposit / redeem combination."; +} + +rule inverseRedeemDepositInFavourForVault(uint256 shares, address deposit_receiver, address redeem_receiver, address redeem_owner){ + env e; + safeAssumptions(); - //Loading values to displaying them in Certora variable view. - uint256 assetBalanceAfter = __ERC20.balanceOf(e.msg.sender); - uint256 sharesBalanceAfter = balanceOf(e.msg.sender); + assumeBalanceEqualSumManualERC20_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); + uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); + uint256 depositedShares = deposit(e, redeemedAssets, deposit_receiver); - assert shares >= withdrawnShares && assetBalanceBefore >= assetBalanceAfter, "User cannot gain assets using deposit / redeem combination."; + assert shares >= depositedShares, "User cannot gain shares using redeem / deposit combination."; } - -rule inverseRedeemWithdrawInFavourForVault_ConcretizedValues(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; + safeAssumptions(); + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - //Dismiss allowance case - require(e.msg.sender == withdraw_owner); - //Further restrict arguments: Now every address is equal to e.msg.sender. - require(e.msg.sender == mint_receiver); - require(e.msg.sender == withdraw_receiver); - - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); - - require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); - - //TODO introduce a fifths person... != withdraw_owner - uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; + assert shares >= withdrawnShares, "User cannot gain assets using mint / withdraw combination."; } - - - -rule inverseRedeemWithdrawInFavourForVault_FullyAbstractNoAllowance(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseWithdrawMintInFavourForVault(uint256 assets, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; + safeAssumptions(); + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - //Dismiss allowance case - require(e.msg.sender == withdraw_owner); - //Further restrict arguments: Now every address is equal to e.msg.sender. - - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); - - require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); - - require(shares > 0); - - //TODO introduce a fifths person... != withdraw_owner - uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + uint256 mintedAssets = mint(e, withdrawnShares, mint_receiver); - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; + assert assets >= mintedAssets, "User cannot gain assets using withdraw / mint combination."; } -rule inverseRedeemWithdrawInFavourForVault_ConcretizedShares(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ - env e; - assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - //Dismiss allowance case - require(e.msg.sender == withdraw_owner); - //Further restrict arguments: Now every address is equal to e.msg.sender. - //require(e.msg.sender == mint_receiver); - // require(e.msg.sender == withdraw_receiver); +//TODO: Not sure if this is even a valid property: The rule fails. +rule redeemInOneTransactionIsPreferable(address user, address receiver, uint256 s1, uint256 s2) { + env e; - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); + safeAssumptions(); + uint256 shares = require_uint256(s1 + s2); + //The below requires have been added to find more intuitive counter examples. require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); + require(e.msg.sender != user); + require(e.msg.sender != receiver); + require(user != receiver); + require(totalAssets() >= totalSupply()); - + storage init = lastStorage; - require(shares == 10000 || shares == 100000 || shares == 0xffff); - //TODO introduce a fifths person... != withdraw_owner - uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; + mathint redeemed1a = redeem(e, s1, receiver, user); + mathint redeemed1b = redeem(e, s2, receiver, user); + mathint redeemed2 = redeem(e, shares, receiver, user) at init; + + assert(redeemed2 <= redeemed1a + redeemed1b); } \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec index 5ece647..22569cb 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RoundingProps.spec @@ -1,8 +1,15 @@ +import "./ERC4626-MonotonicityInvariant.spec"; //Had to change _ERC20 to ___ERC20 as of import that already declares __ERC20. using ERC20 as __ERC20; - - +//This is counter-intuitive: why we need to import invariants that should be loaded when calling safeAssumptions()? +use invariant totalAssetsZeroImpliesTotalSupplyZero; +use invariant sumOfBalancesEqualsTotalSupplyERC4626; +use invariant sumOfBalancesEqualsTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC20; +use invariant singleUserBalanceSmallerThanTotalSupplyERC4626; +use invariant mirrorIsCorrectERC20; +use invariant mirrorIsCorrectERC4626; methods{ function balanceOf(address) external returns uint256 envfree; @@ -13,49 +20,6 @@ methods{ function totalSupply() external returns uint256 envfree; function previewWithdraw(uint256 assets) external returns uint256 envfree; function previewRedeem(uint256 shares) external returns uint256 envfree; - // function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - //function previewWithdraw(uint256 assets) internal returns uint256 => previewWithdrawSummary(assets); - //function previewRedeem(uint256 shares) internal returns uint256 => previewRedeemSummary(shares); -} -function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; -/* // require(res * denominator) <= x * y; -// require((res + 1) * denominator) > x * y; -*/ - - require(res * denominator) <= x * y; - require((res + 1) * denominator) >= x * y; - require x <= denominator; - require res <= y; - require x == 0 => res == 0; - require denominator > 0; - return res; - //return require_uint256(x * y / denominator); -} - - -//The ghost is supposed to cover the equation: assets >= converToAssets(convertToShares(assets)). -ghost uint256 lastCallConvertToShares_AssetsParameter{ - init_state axiom lastCallConvertToShares_AssetsParameter == 0; -} -//The ghost is supposed to cover the equation: shares >= convertToShares(convertToAssets(shares)) -ghost uint256 lastCallConvertToAssets_SharesParameter{ - init_state axiom lastCallConvertToAssets_SharesParameter == 0; -} - -function previewWithdrawSummary(uint256 assets) returns uint256 { - lastCallConvertToShares_AssetsParameter = assets; - uint256 convertedShares = previewWithdraw(assets); - require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); - return convertedShares; -} - -function previewRedeemSummary(uint256 shares) returns uint256 { - lastCallConvertToAssets_SharesParameter = shares; - uint256 convertedAssets = previewRedeem(shares); - - require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); - return convertedAssets; } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ @@ -127,132 +91,78 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; } -rule inverseMintWithdrawInFavourForVault_LessRestrictive(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseDepositRedeemInFavourForVault(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_owner){ env e; - assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - - //Dismiss allowance case - //"Activating" allowance case by removing this requires - //require(e.msg.sender == withdraw_owner); + safeAssumptions(); - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); + assumeBalanceEqualSumManualERC20_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); - require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); - require(withdraw_owner != __ERC20); - require(withdraw_receiver != __ERC20); - require(withdraw_owner != currentContract); - require(withdraw_receiver != currentContract); - require(withdraw_receiver == e.msg.sender); + uint256 shares = deposit(e, assets, deposit_receiver); + uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); + + assert assets >= redeemedAssets, "User cannot gain assets using deposit / redeem combination."; +} - //Loading values to displaying them in Certora variable view. - uint256 assetBalanceBefore = __ERC20.balanceOf(e.msg.sender); - uint256 sharesBalanceBefore = balanceOf(e.msg.sender); +rule inverseRedeemDepositInFavourForVault(uint256 shares, address deposit_receiver, address redeem_receiver, address redeem_owner){ + env e; + safeAssumptions(); - uint256 assets = mint(e, shares, mint_receiver); + assumeBalanceEqualSumManualERC20_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); + assumeBalanceEqualSumManualERC4626_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); - //Loading values to displaying them in Certora variable view. - uint256 assetBalanceIntermediate = __ERC20.balanceOf(e.msg.sender); - uint256 sharesBalanceIntermediate = balanceOf(e.msg.sender); + uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); + uint256 depositedShares = deposit(e, redeemedAssets, deposit_receiver); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - - //Loading values to displaying them in Certora variable view. - uint256 assetBalanceAfter = __ERC20.balanceOf(e.msg.sender); - uint256 sharesBalanceAfter = balanceOf(e.msg.sender); - - assert shares >= withdrawnShares && assetBalanceBefore >= assetBalanceAfter, "User cannot gain assets using deposit / redeem combination."; + assert shares >= depositedShares, "User cannot gain shares using redeem / deposit combination."; } - -rule inverseRedeemWithdrawInFavourForVault_ConcretizedValues(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; + safeAssumptions(); + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - //Dismiss allowance case - require(e.msg.sender == withdraw_owner); - //Further restrict arguments: Now every address is equal to e.msg.sender. - require(e.msg.sender == mint_receiver); - require(e.msg.sender == withdraw_receiver); - - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); - - require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); - - //TODO introduce a fifths person... != withdraw_owner - uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); + uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; + assert shares >= withdrawnShares, "User cannot gain assets using mint / withdraw combination."; } - - - -rule inverseRedeemWithdrawInFavourForVault_FullyAbstractNoAllowance(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ +rule inverseWithdrawMintInFavourForVault(uint256 assets, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; + safeAssumptions(); + assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - //Dismiss allowance case - require(e.msg.sender == withdraw_owner); - //Further restrict arguments: Now every address is equal to e.msg.sender. - - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); - - require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); - - require(shares > 0); - - //TODO introduce a fifths person... != withdraw_owner - uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + uint256 mintedAssets = mint(e, withdrawnShares, mint_receiver); - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; + assert assets >= mintedAssets, "User cannot gain assets using withdraw / mint combination."; } -rule inverseRedeemWithdrawInFavourForVault_ConcretizedShares(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ - env e; - assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - //Dismiss allowance case - require(e.msg.sender == withdraw_owner); - //Further restrict arguments: Now every address is equal to e.msg.sender. - //require(e.msg.sender == mint_receiver); - // require(e.msg.sender == withdraw_receiver); +//TODO: Not sure if this is even a valid property: The rule fails. +rule redeemInOneTransactionIsPreferable(address user, address receiver, uint256 s1, uint256 s2) { + env e; - //Make all non zero to avoid unnecessary cases. - require(e.msg.sender != 0); - require(mint_receiver != 0); - require(withdraw_owner != 0); - require(withdraw_receiver != 0); + safeAssumptions(); + uint256 shares = require_uint256(s1 + s2); + //The below requires have been added to find more intuitive counter examples. require(e.msg.sender != currentContract); - require(e.msg.sender != __ERC20); + require(e.msg.sender != user); + require(e.msg.sender != receiver); + require(user != receiver); + require(totalAssets() >= totalSupply()); - + storage init = lastStorage; - require(shares == 10000 || shares == 100000 || shares == 0xffff); - //TODO introduce a fifths person... != withdraw_owner - uint256 assets = redeem(e, shares, mint_receiver, withdraw_owner); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - - assert shares >= withdrawnShares, "User cannot gain assets using deposit / redeem combination."; + mathint redeemed1a = redeem(e, s1, receiver, user); + mathint redeemed1b = redeem(e, s2, receiver, user); + mathint redeemed2 = redeem(e, shares, receiver, user) at init; + + assert(redeemed2 <= redeemed1a + redeemed1b); } \ No newline at end of file From 711d927395fab3b55505c275ea11579de873bb6f Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:00:29 +0200 Subject: [PATCH 098/125] First summary of mulDiv --- .../certora/conf-openzeppelin/certoraRun-RoundingProps.conf | 4 ---- .../certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index 45aa501..8041086 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -6,10 +6,6 @@ "verify": "ERC4626Mock:src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec", "solc": "solc8.20", "link": ["ERC4626Mock:_asset=ERC20Mock"], - "prover_args": [ - "-smt_hashingScheme plainInjectivity" , - "-solvers [yices,z3]", - ], "server": "production", "send_only": true, "rule_sanity": "basic", diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 592ff3f..7082811 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -20,6 +20,11 @@ methods{ function totalSupply() external returns uint256 envfree; function previewWithdraw(uint256 assets) external returns uint256 envfree; function previewRedeem(uint256 shares) external returns uint256 envfree; + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); +} + +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { + return require_uint256(x * y / denominator); } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ From e7151537b1c71220529827d7f5d3a73d88bf2578 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:03:12 +0200 Subject: [PATCH 099/125] Removing mulDivSummary from parent spec file --- .../ERC4626-MonotonicityInvariant.spec | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec index 1427886..827c685 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec @@ -1,5 +1,5 @@ -using ERC20Mock as _ERC20; +using ERC20 as _ERC20; methods { function totalSupply() external returns uint256 envfree; @@ -12,9 +12,6 @@ methods { function previewRedeem(uint256) external returns uint256 envfree; function _ERC20.totalSupply() external returns uint256 envfree; function _ERC20.balanceOf(address) external returns uint256 envfree; - function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); - //function ERC20._update(address from, address to, uint256 value) internal => updateSafe(from, to, value); } @@ -35,13 +32,6 @@ function safeAssumptionsERC20() { requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; } -function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; - require(res * denominator) <= x * y; - require(res * denominator) > x * y - 1; - return res; -} - //TODO: Careful here: we have ERC462 and ERC20 balance and totalSupply... function updateSafe(address from, address to, uint256 amount){ uint256 balanceOfTo = balanceOf(to); From ce6ebc68dc8fe7733047b03e1b6f231338c41d3f Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:04:53 +0200 Subject: [PATCH 100/125] Fix rule --- .../specs-openzeppelin/ERC4626-MonotonicityInvariant.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec index 827c685..4b77fae 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec @@ -1,5 +1,5 @@ -using ERC20 as _ERC20; +using ERC20Mock as _ERC20; methods { function totalSupply() external returns uint256 envfree; From feee058be000153d7ab6f76ef105951b475befe5 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:06:03 +0200 Subject: [PATCH 101/125] Use lower and upper bounds to express summary of mulDiv // remove unused code --- .../ERC4626-MonotonicityInvariant.spec | 25 ------------------- .../ERC4626-RoundingProps.spec | 5 +++- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec index 4b77fae..1abae1f 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec @@ -32,31 +32,6 @@ function safeAssumptionsERC20() { requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; } -//TODO: Careful here: we have ERC462 and ERC20 balance and totalSupply... -function updateSafe(address from, address to, uint256 amount){ - uint256 balanceOfTo = balanceOf(to); - uint256 balanceOfFrom = balanceOf(from); - uint256 totalSupply = totalSupply(); - - - //transfer or mint case - if(to != 0 && from != to){ - require(balanceOfTo >= amount); - } - //transfer or burn case - if(from != 0 && from != to){ - require(balanceOfFrom >= amount); - } - //mint case - if(from == 0 && to != 0){ - require(totalSupply >= amount); - } - //burn case - if(from != 0 && to == 0){ - require(totalSupply >= amount); - } -} - rule assetAndShareMonotonicy(){ safeAssumptions(); diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 7082811..591e33f 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -24,7 +24,10 @@ methods{ } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - return require_uint256(x * y / denominator); + uint256 res; + require(res * denominator) <= x * y; + require((res + 1) * denominator) > x * y; + return res; } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ From fcf1aec9cc8c39e0bc006675e58961530cf5b4ce Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:10:04 +0200 Subject: [PATCH 102/125] Relaxation of mulDivSummary --- .../specs-openzeppelin/ERC4626-RoundingProps.spec | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 591e33f..641f2f4 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -25,8 +25,13 @@ methods{ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { uint256 res; - require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; + //shares <= totalSupply() and assets <= totalAssets + require x <= denominator; + + //follows from above information: when x/demnominator <= 1 then, res will be at most y. + require res <= y; + require x == 0 => res == 0; + require denominator > 0; return res; } From 8bb3b6ce7da0ead6e28a072c3cedbffde6c99b0f Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:21:06 +0200 Subject: [PATCH 103/125] Adding requirements to rule to find interesting CEXs // removing unnecessary rules for documentation purpose --- .../ERC4626-RoundingProps.spec | 83 ++++--------------- 1 file changed, 14 insertions(+), 69 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 641f2f4..a20f4df 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -24,15 +24,7 @@ methods{ } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; - //shares <= totalSupply() and assets <= totalAssets - require x <= denominator; - - //follows from above information: when x/demnominator <= 1 then, res will be at most y. - require res <= y; - require x == 0 => res == 0; - require denominator > 0; - return res; + return require_uint256(x * y / denominator); } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ @@ -104,31 +96,6 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address require addr1 == addr2 && addr2 == addr3 && addr3 == addr4 => totalSupply == balanceOfAddr1; } -rule inverseDepositRedeemInFavourForVault(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_owner){ - env e; - safeAssumptions(); - - assumeBalanceEqualSumManualERC20_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); - - uint256 shares = deposit(e, assets, deposit_receiver); - uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); - - assert assets >= redeemedAssets, "User cannot gain assets using deposit / redeem combination."; -} - -rule inverseRedeemDepositInFavourForVault(uint256 shares, address deposit_receiver, address redeem_receiver, address redeem_owner){ - env e; - safeAssumptions(); - - assumeBalanceEqualSumManualERC20_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(deposit_receiver,redeem_receiver, redeem_owner, e.msg.sender); - - uint256 redeemedAssets = redeem(e, shares, redeem_receiver, redeem_owner); - uint256 depositedShares = deposit(e, redeemedAssets, deposit_receiver); - - assert shares >= depositedShares, "User cannot gain shares using redeem / deposit combination."; -} rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; @@ -137,45 +104,23 @@ rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - uint256 assets = mint(e, shares, mint_receiver); - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - - assert shares >= withdrawnShares, "User cannot gain assets using mint / withdraw combination."; -} - -rule inverseWithdrawMintInFavourForVault(uint256 assets, address mint_receiver, address withdraw_receiver, address withdraw_owner){ - env e; - safeAssumptions(); - - assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); - - uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); - uint256 mintedAssets = mint(e, withdrawnShares, mint_receiver); - - assert assets >= mintedAssets, "User cannot gain assets using withdraw / mint combination."; -} - -//TODO: Not sure if this is even a valid property: The rule fails. -rule redeemInOneTransactionIsPreferable(address user, address receiver, uint256 s1, uint256 s2) { - env e; + //Dismiss allowance case to simplify proving (less cases to consider.) + require(e.msg.sender == withdraw_owner); - safeAssumptions(); - uint256 shares = require_uint256(s1 + s2); + //Make all non zero to avoid unnecessary cases. + require(e.msg.sender != 0); + require(mint_receiver != 0); + require(withdraw_owner != 0); + require(withdraw_receiver != 0); - //The below requires have been added to find more intuitive counter examples. + //Exclude cases that are not interestinig. require(e.msg.sender != currentContract); - require(e.msg.sender != user); - require(e.msg.sender != receiver); - require(user != receiver); - require(totalAssets() >= totalSupply()); - - storage init = lastStorage; + require(e.msg.sender != __ERC20); - mathint redeemed1a = redeem(e, s1, receiver, user); - mathint redeemed1b = redeem(e, s2, receiver, user); - mathint redeemed2 = redeem(e, shares, receiver, user) at init; - assert(redeemed2 <= redeemed1a + redeemed1b); + uint256 assets = mint(e, shares, mint_receiver); + uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); + + assert shares >= withdrawnShares, "User cannot gain assets using mint / withdraw combination."; } \ No newline at end of file From 358eff9d8c51c239b13aecff15457cb359d8f706 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:24:19 +0200 Subject: [PATCH 104/125] Use lower and upper bounds to express summary of mulDiv --- .../certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index a20f4df..ef65143 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -24,7 +24,10 @@ methods{ } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - return require_uint256(x * y / denominator); + uint256 res; + require(res * denominator) <= x * y; + require((res + 1) * denominator) > x * y; + return res; } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ From 33c3f0d9e337527935db6c3e2898c279bc5e60bc Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:26:05 +0200 Subject: [PATCH 105/125] Full relaxation of mulDivSummary --- .../specs-openzeppelin/ERC4626-RoundingProps.spec | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index ef65143..5937565 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -24,9 +24,13 @@ methods{ } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; - require(res * denominator) <= x * y; - require((res + 1) * denominator) > x * y; + uint256 res; //shares <= totalSupply() and assets <= totalAssets + require x <= denominator; + + //follows from above information: when x/demnominator <= 1 then, res will be at most y. + require res <= y; + require x == 0 => res == 0; + require denominator > 0; return res; } From 51741e8a7f0cd3b61fafea2332f243e7818b30d8 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:38:00 +0200 Subject: [PATCH 106/125] Removing safeAssumption to see if we run into a timeout --- .../src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 1 - 1 file changed, 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 5937565..9905144 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -106,7 +106,6 @@ function assumeBalanceEqualSumManualERC20_4(address addr1,address addr2,address rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, address withdraw_receiver, address withdraw_owner){ env e; - safeAssumptions(); assumeBalanceEqualSumManualERC20_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); assumeBalanceEqualSumManualERC4626_4(mint_receiver,withdraw_receiver, withdraw_owner, e.msg.sender); From b196cc21c6018234f6198e08f8e75ee9250991f3 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 11:46:39 +0200 Subject: [PATCH 107/125] No safeAssumptions & requireuint summary --- .../specs-openzeppelin/ERC4626-RoundingProps.spec | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 9905144..f6acbcb 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -24,14 +24,7 @@ methods{ } function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; //shares <= totalSupply() and assets <= totalAssets - require x <= denominator; - - //follows from above information: when x/demnominator <= 1 then, res will be at most y. - require res <= y; - require x == 0 => res == 0; - require denominator > 0; - return res; + return require_uint256(x*y / denominator); } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ From 2f6b66daadeba91a598489cc176bb6a939204d2a Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 12:29:57 +0200 Subject: [PATCH 108/125] Using relaxed summary for update function --- .../ERC4626-RoundingProps.spec | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index f6acbcb..964cb7c 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -20,11 +20,32 @@ methods{ function totalSupply() external returns uint256 envfree; function previewWithdraw(uint256 assets) external returns uint256 envfree; function previewRedeem(uint256 shares) external returns uint256 envfree; - function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); + + function ERC20._update(address from, address to, uint256 amount) internal => updateSafe(from, to, amount); } -function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - return require_uint256(x*y / denominator); +//TODO: Careful here: we have ERC462 and ERC20 balance and totalSupply... +function updateSafe(address from, address to, uint256 amount){ + uint256 balanceOfTo = balanceOf(to); + uint256 balanceOfFrom = balanceOf(from); + uint256 totalSupply = totalSupply(); + + //transfer or mint case + if(to != 0 && from != to){ + require(balanceOfTo >= amount); + } + //transfer or burn case + if(from != 0 && from != to){ + require(balanceOfFrom >= amount); + } + //mint case + if(from == 0 && to != 0){ + require(totalSupply >= amount); + } + //burn case + if(from != 0 && to == 0){ + require(totalSupply >= amount); + } } function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ From 5339cbbcd90065db761e5bbc781915f3aea464a2 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 13:00:39 +0200 Subject: [PATCH 109/125] Using fully relaxed summary in combination with ghost to bound results of convertToAssets/convertToShares --- .../ERC4626-RoundingProps.spec | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 964cb7c..20aa27b 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -18,35 +18,51 @@ methods{ function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; - function previewWithdraw(uint256 assets) external returns uint256 envfree; - function previewRedeem(uint256 shares) external returns uint256 envfree; - function ERC20._update(address from, address to, uint256 amount) internal => updateSafe(from, to, amount); + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); + + function previewWithdraw(uint256 assets) external returns uint256 envfree => previewWithdrawSummary(assets); + function previewMint(uint256 shares) external returns uint256 envfree => previewMintSummary(shares); +} + +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { + uint256 res; + //shares <= totalSupply() and assets <= totalAssets + require x <= denominator; + + //follows from above information: when x/demnominator <= 1 then, res will be at most y. + require res <= y; + require x == 0 => res == 0; + require denominator > 0; + return res; } -//TODO: Careful here: we have ERC462 and ERC20 balance and totalSupply... -function updateSafe(address from, address to, uint256 amount){ - uint256 balanceOfTo = balanceOf(to); - uint256 balanceOfFrom = balanceOf(from); - uint256 totalSupply = totalSupply(); - - //transfer or mint case - if(to != 0 && from != to){ - require(balanceOfTo >= amount); - } - //transfer or burn case - if(from != 0 && from != to){ - require(balanceOfFrom >= amount); - } - //mint case - if(from == 0 && to != 0){ - require(totalSupply >= amount); - } - //burn case - if(from != 0 && to == 0){ - require(totalSupply >= amount); - } +//The ghost is supposed to cover the equation: assets >= converToAssets(convertToShares(assets)). +ghost uint256 lastCallConvertToShares_AssetsParameter{ + init_state axiom lastCallConvertToShares_AssetsParameter == 0; } +//The ghost is supposed to cover the equation: shares >= convertToShares(convertToAssets(shares)) +ghost uint256 lastCallConvertToAssets_SharesParameter{ + init_state axiom lastCallConvertToAssets_SharesParameter == 0; +} + +function previewWithdrawSummary(uint256 assets) returns uint256 { + //previewWithdraw calls convertToShares + lastCallConvertToShares_AssetsParameter = assets; + uint256 convertedShares = previewWithdraw(assets); + require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); + return convertedShares; +} + +function previewMintSummary(uint256 shares) returns uint256 { + //previewWithdraw calls convertToAssets + lastCallConvertToAssets_SharesParameter = shares; + uint256 convertedAssets = previewMint(shares); + require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); + return convertedAssets; +} + + function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ mathint totalSupply = totalSupply(); From a7ca05e5a4348477c1fdea76ac06d4f6802fd9a2 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 13:34:56 +0200 Subject: [PATCH 110/125] Fixing shares to 10000 --- .../ERC4626-RoundingProps.spec | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 20aa27b..9042c6d 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -18,51 +18,8 @@ methods{ function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; - - function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); - - function previewWithdraw(uint256 assets) external returns uint256 envfree => previewWithdrawSummary(assets); - function previewMint(uint256 shares) external returns uint256 envfree => previewMintSummary(shares); } -function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { - uint256 res; - //shares <= totalSupply() and assets <= totalAssets - require x <= denominator; - - //follows from above information: when x/demnominator <= 1 then, res will be at most y. - require res <= y; - require x == 0 => res == 0; - require denominator > 0; - return res; -} - -//The ghost is supposed to cover the equation: assets >= converToAssets(convertToShares(assets)). -ghost uint256 lastCallConvertToShares_AssetsParameter{ - init_state axiom lastCallConvertToShares_AssetsParameter == 0; -} -//The ghost is supposed to cover the equation: shares >= convertToShares(convertToAssets(shares)) -ghost uint256 lastCallConvertToAssets_SharesParameter{ - init_state axiom lastCallConvertToAssets_SharesParameter == 0; -} - -function previewWithdrawSummary(uint256 assets) returns uint256 { - //previewWithdraw calls convertToShares - lastCallConvertToShares_AssetsParameter = assets; - uint256 convertedShares = previewWithdraw(assets); - require(lastCallConvertToAssets_SharesParameter != 0 => lastCallConvertToAssets_SharesParameter >= convertedShares); - return convertedShares; -} - -function previewMintSummary(uint256 shares) returns uint256 { - //previewWithdraw calls convertToAssets - lastCallConvertToAssets_SharesParameter = shares; - uint256 convertedAssets = previewMint(shares); - require(lastCallConvertToShares_AssetsParameter != 0 => lastCallConvertToShares_AssetsParameter >= convertedAssets); - return convertedAssets; -} - - function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ mathint totalSupply = totalSupply(); @@ -154,6 +111,7 @@ rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); + require(shares == 10000); uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From e807845db3edc7770ff72c4803cdee9fa34cf81c Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 13:35:55 +0200 Subject: [PATCH 111/125] Fixing addresses --- .../src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index 9042c6d..c67122a 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -111,7 +111,9 @@ rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); - require(shares == 10000); + require(e.msg.sender == 2); + require(mint_receiver == 3); + require(withdraw_receiver == 4); uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From f62f9adcec0599aca73199e1d57bf0a60efad6b7 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Tue, 19 Sep 2023 13:50:32 +0200 Subject: [PATCH 112/125] Using yices --- .../certora/conf-openzeppelin/certoraRun-RoundingProps.conf | 6 +++++- .../certora/specs-openzeppelin/ERC4626-RoundingProps.spec | 4 ---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf index 8041086..b49e79d 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf @@ -5,7 +5,11 @@ ], "verify": "ERC4626Mock:src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec", "solc": "solc8.20", - "link": ["ERC4626Mock:_asset=ERC20Mock"], + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "prover_args": [ + "-smt_hashingScheme plainInjectivity" , + "-solvers [yices,z3]", + ], "server": "production", "send_only": true, "rule_sanity": "basic", diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index c67122a..ad79bf1 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -111,10 +111,6 @@ rule inverseMintWithdrawInFavourForVault(uint256 shares, address mint_receiver, require(e.msg.sender != currentContract); require(e.msg.sender != __ERC20); - require(e.msg.sender == 2); - require(mint_receiver == 3); - require(withdraw_receiver == 4); - uint256 assets = mint(e, shares, mint_receiver); uint256 withdrawnShares = withdraw(e, assets, withdraw_receiver, withdraw_owner); From 76f3c8c8525afd451b87b60d1fd77c4d1d406e84 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Thu, 21 Sep 2023 08:17:08 +0200 Subject: [PATCH 113/125] Basic version of inflation attack working for OpenZeppelin and base implementation --- .../certoraRun-InflationAttack.conf | 8 +++- .../ERC4626-InflationAttack.spec | 40 +++++++++++++++++-- .../ERC4626-RoundingProps.spec | 14 +++++++ .../specs/ERC4626-InflationAttack.spec | 2 +- .../ERC4626-RedeemUsingApprovalProps.spec | 2 +- 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf index b7f8a12..7d8ada1 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf @@ -5,11 +5,15 @@ ], "verify": "ERC4626Mock:src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec", "link": ["ERC4626Mock:_asset=ERC20Mock"], + "prover_args":[ + "-smt_hashingScheme plainInjectivity", + "-solvers [yices,z3]" + ], + "server": "production", "solc": "solc8.20", "prover_version": "master", - "server": "production", "rule_sanity": "basic", "send_only": true, "msg": "Verification of ERC4626 OpenZeppelin", - "optimistic_loop": true + "optimistic_loop": true, } \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec index 32b1fff..5cbeee4 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec @@ -29,11 +29,40 @@ methods{ function previewWithdraw(uint256) external returns uint256 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; + + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); +} + +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { + return require_uint256(x*y/denominator); } +/// @title the loss while redeeming is limited +/// - better to redeem at once +/// - difference should be at most two +rule userLossIsLimited(address user, address receiver, uint256 s1, uint256 s2) { + env e; + require(receiver != currentContract); + require(user == e.msg.sender); + require(totalAssets() > 0); + require(totalSupply() > 0); + uint256 shares = require_uint256(s1 + s2); + require(shares <= balanceOf(e, user)); + require(shares <= totalSupply()); + + storage init = lastStorage; + + mathint redeemed1a = redeem(e, s1, receiver, user); + mathint redeemed1b = redeem(e, s2, receiver, user); + mathint redeemed2 = redeem(e, shares, receiver, user) at init; + + assert(redeemed2 >= redeemed1a + redeemed1b); + assert(redeemed2 <= redeemed1a + redeemed1b + 2); +} + -rule simpleVersionOfVulnerableAttack(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_ownver) { +rule simpleVersionOfInflationAttack(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_ownver) { env e; safeAssumptions(); address attacker = e.msg.sender; @@ -48,17 +77,17 @@ rule simpleVersionOfVulnerableAttack(uint256 assets, address deposit_receiver, a uint256 shares = deposit(e, assets, deposit_receiver); //In the inflationAttack there are 2 steps that we don't model here! - uint256 receivedAssets = redeem(e, shares, redeem_receiver, redeem_ownver); assert(receivedAssets <= assets); } - //Source: Medium Article by Shao https://tienshaoku.medium.com/eip-4626-inflation-sandwich-attack-deep-dive-and-how-to-solve-it-9e3e320cc3f1 rule vulnerableToInflationAttack(address attacker, address victim, address deposit1_receiver, address deposit2_victim_receiver,address redeem_receiver,address redeem_ownver ){ + + //Doesn't work properly...Retry later. /*requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; @@ -99,6 +128,11 @@ rule vulnerableToInflationAttack(address attacker, address victim, address depos require(balanceOf(deposit2_victim_receiver) == 0); require(balanceOf(redeem_receiver) == 0); require(balanceOf(redeem_ownver) == 0); + + + require(balanceOf(attacker) + balanceOf(victim) + balanceOf(deposit1_receiver) +balanceOf(deposit2_victim_receiver) +balanceOf(redeem_receiver) + balanceOf(redeem_ownver) <= to_mathint(totalSupply())); + require(__ERC20.balanceOf(attacker) + __ERC20.balanceOf(victim) + __ERC20.balanceOf(deposit1_receiver) +__ERC20.balanceOf(deposit2_victim_receiver) +__ERC20.balanceOf(redeem_receiver) + __ERC20.balanceOf(redeem_ownver) <= to_mathint(__ERC20.totalSupply())); + uint256 before_step_1_totalSupply = totalSupply(); uint256 before_step_1_totalAssets = totalAssets(); diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec index ad79bf1..5598045 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec @@ -18,8 +18,22 @@ methods{ function decimals() external returns uint8 envfree; function totalAssets() external returns uint256 envfree; function totalSupply() external returns uint256 envfree; + + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator, Math.Rounding round) internal returns uint256 => mulDivSummary(x,y,denominator, round); } +function mulDivSummary(uint256 x, uint256 y, uint256 denominator, Math.Rounding round) returns uint256 { + uint256 res; + //shares <= totalSupply() and assets <= totalAssets + require x <= denominator; + + //follows from above information: when x/demnominator <= 1 then, res will be at most y. + require res <= y; + require x == 0 => res == 0; + require denominator > 0; + return res; +} + function assumeBalanceEqualSumManualERC4626_4(address addr1,address addr2,address addr3, address addr4){ mathint totalSupply = totalSupply(); diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec index a9f14b8..7094279 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec @@ -33,7 +33,7 @@ methods{ -rule simpleVersionOfVulnerableAttack(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_ownver) { +rule simpleVersionOfInflationAttack(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_ownver) { env e; safeAssumptions(); address attacker = e.msg.sender; diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec index fecdb3c..5a972a4 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-RedeemUsingApprovalProps.spec @@ -1,4 +1,4 @@ -using ERC20Mock as _ERC20; +using ERC20 as _ERC20; methods { function _ERC20.balanceOf(address) external returns uint256 envfree; From abcc64b8f5d7135a797ca43c0e10d24067b30350 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Thu, 21 Sep 2023 08:47:33 +0200 Subject: [PATCH 114/125] Duplicating code to separate buggy and non buggy version --- .../contracts copy/interfaces/IERC4626.sol | 230 ++++++++++ .../interfaces/draft-IERC6093.sol | 160 +++++++ .../contracts copy/mocks/token/DummyERC20.sol | 16 + .../contracts copy/mocks/token/ERC20Mock.sol | 16 + .../mocks/token/ERC4626Mock.sol | 17 + .../contracts copy/token/ERC20/ERC20.sol | 372 ++++++++++++++++ .../contracts copy/token/ERC20/IERC20.sol | 79 ++++ .../token/ERC20/extensions/ERC4626.sol | 319 ++++++++++++++ .../token/ERC20/extensions/IERC20Metadata.sol | 26 ++ .../token/ERC20/extensions/IERC20Permit.sol | 60 +++ .../token/ERC20/utils/SafeERC20.sol | 140 ++++++ .../contracts copy/utils/Address.sol | 159 +++++++ .../contracts copy/utils/Context.sol | 24 + .../contracts copy/utils/math/Math.sol | 414 ++++++++++++++++++ .../contracts/interfaces/IERC4626.sol | 230 ++++++++++ .../contracts/interfaces/draft-IERC6093.sol | 160 +++++++ .../contracts/mocks/token/DummyERC20.sol | 16 + .../contracts/mocks/token/ERC20Mock.sol | 16 + .../contracts/mocks/token/ERC4626Mock.sol | 17 + .../contracts/token/ERC20/ERC20.sol | 372 ++++++++++++++++ .../contracts/token/ERC20/IERC20.sol | 79 ++++ .../token/ERC20/extensions/ERC4626.sol | 319 ++++++++++++++ .../token/ERC20/extensions/IERC20Metadata.sol | 26 ++ .../token/ERC20/extensions/IERC20Permit.sol | 60 +++ .../contracts/token/ERC20/utils/SafeERC20.sol | 140 ++++++ .../contracts/utils/Address.sol | 159 +++++++ .../contracts/utils/Context.sol | 24 + .../contracts/utils/math/Math.sol | 414 ++++++++++++++++++ .../contracts copy/interfaces/IERC4626.sol | 230 ++++++++++ .../interfaces/draft-IERC6093.sol | 160 +++++++ .../contracts copy/mocks/token/DummyERC20.sol | 16 + .../contracts copy/mocks/token/ERC20Mock.sol | 16 + .../mocks/token/ERC4626Mock.sol | 17 + .../contracts copy/token/ERC20/ERC20.sol | 372 ++++++++++++++++ .../contracts copy/token/ERC20/IERC20.sol | 79 ++++ .../token/ERC20/extensions/ERC4626.sol | 319 ++++++++++++++ .../token/ERC20/extensions/IERC20Metadata.sol | 26 ++ .../token/ERC20/extensions/IERC20Permit.sol | 60 +++ .../token/ERC20/utils/SafeERC20.sol | 140 ++++++ .../contracts copy/utils/Address.sol | 159 +++++++ .../contracts copy/utils/Context.sol | 24 + .../contracts copy/utils/math/Math.sol | 414 ++++++++++++++++++ .../contracts/interfaces/IERC4626.sol | 230 ++++++++++ .../contracts/interfaces/draft-IERC6093.sol | 160 +++++++ .../contracts/mocks/token/DummyERC20.sol | 16 + .../contracts/mocks/token/ERC20Mock.sol | 16 + .../contracts/mocks/token/ERC4626Mock.sol | 17 + .../contracts/token/ERC20/ERC20.sol | 372 ++++++++++++++++ .../contracts/token/ERC20/IERC20.sol | 79 ++++ .../token/ERC20/extensions/ERC4626.sol | 319 ++++++++++++++ .../token/ERC20/extensions/IERC20Metadata.sol | 26 ++ .../token/ERC20/extensions/IERC20Permit.sol | 60 +++ .../contracts/token/ERC20/utils/SafeERC20.sol | 140 ++++++ .../contracts/utils/Address.sol | 159 +++++++ .../contracts/utils/Context.sol | 24 + .../contracts/utils/math/Math.sol | 414 ++++++++++++++++++ 56 files changed, 8128 insertions(+) create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/IERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/draft-IERC6093.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/DummyERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC20Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC4626Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/ERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/IERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/ERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Metadata.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Permit.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/utils/SafeERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Address.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Context.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/math/Math.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/IERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/draft-IERC6093.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/DummyERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC20Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC4626Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/ERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/IERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Metadata.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Permit.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/utils/SafeERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Address.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Context.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/IERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/draft-IERC6093.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/DummyERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC20Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC4626Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/ERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/IERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/ERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Metadata.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Permit.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/utils/SafeERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Address.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Context.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/math/Math.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/IERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/draft-IERC6093.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/DummyERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC20Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC4626Mock.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/ERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/IERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/ERC4626.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/utils/SafeERC20.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Address.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Context.sol create mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/math/Math.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/IERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/IERC4626.sol new file mode 100644 index 0000000..e1b778e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + 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 + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/draft-IERC6093.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/draft-IERC6093.sol new file mode 100644 index 0000000..c38379a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/draft-IERC6093.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @dev Standard ERC20 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + */ +interface IERC20Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC20InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC20InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. + * @param spender Address that may be allowed to operate on tokens without being their owner. + * @param allowance Amount of tokens a `spender` is allowed to operate with. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC20InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `spender` to be approved. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC20InvalidSpender(address spender); +} + +/** + * @dev Standard ERC721 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + */ +interface IERC721Errors { + /** + * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. + * Used in balance queries. + * @param owner Address of the current owner of a token. + */ + error ERC721InvalidOwner(address owner); + + /** + * @dev Indicates a `tokenId` whose `owner` is the zero address. + * @param tokenId Identifier number of a token. + */ + error ERC721NonexistentToken(uint256 tokenId); + + /** + * @dev Indicates an error related to the ownership over a particular token. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param tokenId Identifier number of a token. + * @param owner Address of the current owner of a token. + */ + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC721InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC721InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param tokenId Identifier number of a token. + */ + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC721InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC721InvalidOperator(address operator); +} + +/** + * @dev Standard ERC1155 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. + */ +interface IERC1155Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + * @param tokenId Identifier number of a token. + */ + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC1155InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC1155InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param owner Address of the current owner of a token. + */ + error ERC1155MissingApprovalForAll(address operator, address owner); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC1155InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC1155InvalidOperator(address operator); + + /** + * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + * Used in batch transfers. + * @param idsLength Length of the array of token identifiers + * @param valuesLength Length of the array of token amounts + */ + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/DummyERC20.sol new file mode 100644 index 0000000..bc1376a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/DummyERC20.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract DummyERC20 is ERC20 { + constructor() ERC20("ERC20Mock2", "E20M2") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC20Mock.sol new file mode 100644 index 0000000..39ab129 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC20Mock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor() ERC20("ERC20Mock", "E20M") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC4626Mock.sol new file mode 100644 index 0000000..5ce832e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC4626Mock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +contract ERC4626Mock is ERC4626 { + constructor(address underlying) ERC4626(IERC20(underlying)) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/ERC20.sol new file mode 100644 index 0000000..8eeb314 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/ERC20.sol @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * The default value of {decimals} is 18. To change this, you should override + * this function so it returns a different value. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { + mapping(address account => uint256) private _balances; + + mapping(address account => mapping(address spender => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(address to, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, value); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, value); + _transfer(from, to, value); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `requestedDecrease`. + * + * NOTE: Although this function is designed to avoid double spending with {approval}, + * it can still be frontrunned, preventing any attempt of allowance reduction. + */ + function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance < requestedDecrease) { + revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + unchecked { + _approve(owner, spender, currentAllowance - requestedDecrease); + } + + return true; + } + + /** + * @dev Moves a `value` amount of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(address from, address to, uint256 value) internal { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(from, to, value); + } + + /** + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is + * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. + * + * Emits a {Transfer} event. + */ + function _update(address from, address to, uint256 value) internal virtual { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; + } else { + uint256 fromBalance = _balances[from]; + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[from] = fromBalance - value; + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[to] += value; + } + } + + emit Transfer(from, to, value); + } + + /** + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _mint(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * Relies on the `_update` mechanism. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead + */ + function _burn(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } + _update(account, address(0), value); + } + + /** + * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address owner, address spender, uint256 value) internal { + _approve(owner, spender, value, true); + } + + /** + * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. + * + * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by + * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any + * `Approval` event during `transferFrom` operations. + * + * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true + * using the following override: + * ``` + * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { + * super._approve(owner, spender, value, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _allowances[owner][spender] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/IERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/IERC20.sol new file mode 100644 index 0000000..77ca716 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/ERC4626.sol new file mode 100644 index 0000000..a8296fb --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/ERC4626.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; +import {IERC4626} from "../../../interfaces/IERC4626.sol"; +import {Math} from "../../../utils/math/Math.sol"; + +/** + * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in + * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + * + * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for + * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends + * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this + * contract and not the "assets" token which is an independent contract. + * + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial + * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * verifying the amount received is as expected, using a wrapper that performs these checks such as + * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + */ +contract ERC4626 is ERC20, IERC4626 { + using Math for uint256; + + IERC20 private immutable _asset; + uint8 private immutable _decimals; + + /** + * @dev Attempted to deposit more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @dev Attempted to mint more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @dev Attempted to withdraw more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @dev Attempted to redeem more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + + /** + * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + */ + constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _underlyingDecimals = success ? assetDecimals : 18; + _asset = asset_; + } + + /** + * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. + */ + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( + abi.encodeCall(IERC20Metadata.decimals, ()) + ); + if (success && encodedDecimals.length >= 32) { + uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + if (returnedDecimals <= type(uint8).max) { + return (true, uint8(returnedDecimals)); + } + } + return (false, 0); + } + + /** + * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This + * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the + * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + * + * See {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _decimals; + } + + /** @dev See {IERC4626-asset}. */ + function asset() public view virtual returns (address) { + return address(_asset); + } + + /** @dev See {IERC4626-totalAssets}. */ + function totalAssets() public view virtual returns (uint256) { + return _asset.balanceOf(address(this)); + } + + /** @dev See {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual returns (uint256) { + return _isVaultHealthy() ? type(uint256).max : 0; + } + + /** @dev See {IERC4626-maxMint}. */ + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf(owner); + } + + /** @dev See {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + + uint256 shares = previewDeposit(assets); + _deposit(_msgSender(), receiver, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-mint}. + * + * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. + * In this case, the shares will be minted without requiring any assets to be deposited. + */ + function mint(uint256 shares, address receiver) public virtual returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + + uint256 assets = previewMint(shares); + _deposit(_msgSender(), receiver, assets, shares); + + return assets; + } + + /** @dev See {IERC4626-withdraw}. */ + function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + + uint256 shares = previewWithdraw(assets); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-redeem}. */ + function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + + uint256 assets = previewRedeem(shares); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return assets; + } + + /** + * @dev Internal conversion function (from assets to shares) with support for rounding direction. + */ + function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { + uint256 supply = totalSupply(); + return + (assets == 0 || supply == 0) + ? _initialConvertToShares(assets, rounding) + : assets.mulDiv(supply, totalAssets(), rounding); + } + + /** + * @dev Internal conversion function (from assets to shares) to apply when the vault is empty. + * + * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it. + */ + function _initialConvertToShares( + uint256 assets, + Math.Rounding /*rounding*/ + ) internal view virtual returns (uint256 shares) { + return assets; + } + + /** + * @dev Internal conversion function (from shares to assets) with support for rounding direction. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + uint256 supply = totalSupply(); + return + (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding); + } + + /** + * @dev Internal conversion function (from shares to assets) to apply when the vault is empty. + * + * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it. + */ + function _initialConvertToAssets( + uint256 shares, + Math.Rounding /*rounding*/ + ) internal view virtual returns (uint256) { + return shares; + } + + /** + * @dev Deposit/mint common workflow. + */ + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the + // assets are transferred and before the shares are minted, which is a valid state. + // slither-disable-next-line reentrancy-no-eth + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _mint(receiver, shares); + + emit Deposit(caller, receiver, assets, shares); + } + + /** + * @dev Withdraw/redeem common workflow. + */ + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + + // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the + // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the + // shares are burned and after the assets are transferred, which is a valid state. + _burn(owner, shares); + SafeERC20.safeTransfer(_asset, receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + /** + * @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares. + */ + function _isVaultHealthy() private view returns (bool) { + return totalAssets() > 0 || totalSupply() == 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Metadata.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Metadata.sol new file mode 100644 index 0000000..9056e34 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Permit.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Permit.sol new file mode 100644 index 0000000..2370410 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Permit.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/utils/SafeERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/utils/SafeERC20.sol new file mode 100644 index 0000000..fcdbbae --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/utils/SafeERC20.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; +import {IERC20Permit} from "../extensions/IERC20Permit.sol"; +import {Address} from "../../../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + /** + * @dev An operation with an ERC20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + */ + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); + } + + /** + * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 oldAllowance = token.allowance(address(this), spender); + forceApprove(token, spender, oldAllowance + value); + } + + /** + * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { + unchecked { + uint256 currentAllowance = token.allowance(address(this), spender); + if (currentAllowance < requestedDecrease) { + revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + forceApprove(token, spender, currentAllowance - requestedDecrease); + } + } + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. + */ + function forceApprove(IERC20 token, address spender, uint256 value) internal { + bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); + + if (!_callOptionalReturnBool(token, approvalCall)) { + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); + _callOptionalReturn(token, approvalCall); + } + } + + /** + * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. + * Revert on invalid signature. + */ + function safePermit( + IERC20Permit token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + uint256 nonceBefore = token.nonces(owner); + token.permit(owner, spender, value, deadline, v, r, s); + uint256 nonceAfter = token.nonces(owner); + if (nonceAfter != nonceBefore + 1) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data); + if (returndata.length != 0 && !abi.decode(returndata, (bool))) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false + // and not revert is the subcall reverts. + + (bool success, bytes memory returndata) = address(token).call(data); + return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Address.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Address.sol new file mode 100644 index 0000000..fd22b05 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Address.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error AddressInsufficientBalance(address account); + + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedInnerCall(); + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) { + revert AddressInsufficientBalance(address(this)); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert FailedInnerCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {FailedInnerCall} error. + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + if (address(this).balance < value) { + revert AddressInsufficientBalance(address(this)); + } + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an + * unsuccessful call. + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) internal view returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); + } + return returndata; + } + } + + /** + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {FailedInnerCall} error. + */ + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. + */ + function _revert(bytes memory returndata) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert FailedInnerCall(); + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Context.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Context.sol new file mode 100644 index 0000000..25e1159 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/math/Math.sol new file mode 100644 index 0000000..17ce4c8 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/math/Math.sol @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) + * with further edits by Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. + // See https://cs.stackexchange.com/q/138556/92363. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + } + } + + /** + * @dev Return the log in base 2 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 10 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 256 of a positive value rounded towards zero. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + } + } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/IERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/IERC4626.sol new file mode 100644 index 0000000..e1b778e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + 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 + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/draft-IERC6093.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/draft-IERC6093.sol new file mode 100644 index 0000000..c38379a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/draft-IERC6093.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @dev Standard ERC20 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + */ +interface IERC20Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC20InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC20InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. + * @param spender Address that may be allowed to operate on tokens without being their owner. + * @param allowance Amount of tokens a `spender` is allowed to operate with. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC20InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `spender` to be approved. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC20InvalidSpender(address spender); +} + +/** + * @dev Standard ERC721 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + */ +interface IERC721Errors { + /** + * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. + * Used in balance queries. + * @param owner Address of the current owner of a token. + */ + error ERC721InvalidOwner(address owner); + + /** + * @dev Indicates a `tokenId` whose `owner` is the zero address. + * @param tokenId Identifier number of a token. + */ + error ERC721NonexistentToken(uint256 tokenId); + + /** + * @dev Indicates an error related to the ownership over a particular token. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param tokenId Identifier number of a token. + * @param owner Address of the current owner of a token. + */ + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC721InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC721InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param tokenId Identifier number of a token. + */ + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC721InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC721InvalidOperator(address operator); +} + +/** + * @dev Standard ERC1155 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. + */ +interface IERC1155Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + * @param tokenId Identifier number of a token. + */ + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC1155InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC1155InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param owner Address of the current owner of a token. + */ + error ERC1155MissingApprovalForAll(address operator, address owner); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC1155InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC1155InvalidOperator(address operator); + + /** + * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + * Used in batch transfers. + * @param idsLength Length of the array of token identifiers + * @param valuesLength Length of the array of token amounts + */ + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/DummyERC20.sol new file mode 100644 index 0000000..bc1376a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/DummyERC20.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract DummyERC20 is ERC20 { + constructor() ERC20("ERC20Mock2", "E20M2") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC20Mock.sol new file mode 100644 index 0000000..39ab129 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC20Mock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor() ERC20("ERC20Mock", "E20M") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC4626Mock.sol new file mode 100644 index 0000000..5ce832e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC4626Mock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +contract ERC4626Mock is ERC4626 { + constructor(address underlying) ERC4626(IERC20(underlying)) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/ERC20.sol new file mode 100644 index 0000000..8eeb314 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/ERC20.sol @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * The default value of {decimals} is 18. To change this, you should override + * this function so it returns a different value. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { + mapping(address account => uint256) private _balances; + + mapping(address account => mapping(address spender => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(address to, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, value); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, value); + _transfer(from, to, value); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `requestedDecrease`. + * + * NOTE: Although this function is designed to avoid double spending with {approval}, + * it can still be frontrunned, preventing any attempt of allowance reduction. + */ + function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance < requestedDecrease) { + revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + unchecked { + _approve(owner, spender, currentAllowance - requestedDecrease); + } + + return true; + } + + /** + * @dev Moves a `value` amount of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(address from, address to, uint256 value) internal { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(from, to, value); + } + + /** + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is + * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. + * + * Emits a {Transfer} event. + */ + function _update(address from, address to, uint256 value) internal virtual { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; + } else { + uint256 fromBalance = _balances[from]; + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[from] = fromBalance - value; + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[to] += value; + } + } + + emit Transfer(from, to, value); + } + + /** + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _mint(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * Relies on the `_update` mechanism. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead + */ + function _burn(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } + _update(account, address(0), value); + } + + /** + * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address owner, address spender, uint256 value) internal { + _approve(owner, spender, value, true); + } + + /** + * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. + * + * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by + * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any + * `Approval` event during `transferFrom` operations. + * + * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true + * using the following override: + * ``` + * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { + * super._approve(owner, spender, value, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _allowances[owner][spender] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/IERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/IERC20.sol new file mode 100644 index 0000000..77ca716 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol new file mode 100644 index 0000000..a8296fb --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; +import {IERC4626} from "../../../interfaces/IERC4626.sol"; +import {Math} from "../../../utils/math/Math.sol"; + +/** + * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in + * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + * + * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for + * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends + * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this + * contract and not the "assets" token which is an independent contract. + * + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial + * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * verifying the amount received is as expected, using a wrapper that performs these checks such as + * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + */ +contract ERC4626 is ERC20, IERC4626 { + using Math for uint256; + + IERC20 private immutable _asset; + uint8 private immutable _decimals; + + /** + * @dev Attempted to deposit more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @dev Attempted to mint more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @dev Attempted to withdraw more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @dev Attempted to redeem more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + + /** + * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + */ + constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _underlyingDecimals = success ? assetDecimals : 18; + _asset = asset_; + } + + /** + * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. + */ + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( + abi.encodeCall(IERC20Metadata.decimals, ()) + ); + if (success && encodedDecimals.length >= 32) { + uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + if (returnedDecimals <= type(uint8).max) { + return (true, uint8(returnedDecimals)); + } + } + return (false, 0); + } + + /** + * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This + * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the + * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + * + * See {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _decimals; + } + + /** @dev See {IERC4626-asset}. */ + function asset() public view virtual returns (address) { + return address(_asset); + } + + /** @dev See {IERC4626-totalAssets}. */ + function totalAssets() public view virtual returns (uint256) { + return _asset.balanceOf(address(this)); + } + + /** @dev See {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual returns (uint256) { + return _isVaultHealthy() ? type(uint256).max : 0; + } + + /** @dev See {IERC4626-maxMint}. */ + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf(owner); + } + + /** @dev See {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + + uint256 shares = previewDeposit(assets); + _deposit(_msgSender(), receiver, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-mint}. + * + * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. + * In this case, the shares will be minted without requiring any assets to be deposited. + */ + function mint(uint256 shares, address receiver) public virtual returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + + uint256 assets = previewMint(shares); + _deposit(_msgSender(), receiver, assets, shares); + + return assets; + } + + /** @dev See {IERC4626-withdraw}. */ + function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + + uint256 shares = previewWithdraw(assets); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-redeem}. */ + function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + + uint256 assets = previewRedeem(shares); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return assets; + } + + /** + * @dev Internal conversion function (from assets to shares) with support for rounding direction. + */ + function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { + uint256 supply = totalSupply(); + return + (assets == 0 || supply == 0) + ? _initialConvertToShares(assets, rounding) + : assets.mulDiv(supply, totalAssets(), rounding); + } + + /** + * @dev Internal conversion function (from assets to shares) to apply when the vault is empty. + * + * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it. + */ + function _initialConvertToShares( + uint256 assets, + Math.Rounding /*rounding*/ + ) internal view virtual returns (uint256 shares) { + return assets; + } + + /** + * @dev Internal conversion function (from shares to assets) with support for rounding direction. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + uint256 supply = totalSupply(); + return + (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding); + } + + /** + * @dev Internal conversion function (from shares to assets) to apply when the vault is empty. + * + * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it. + */ + function _initialConvertToAssets( + uint256 shares, + Math.Rounding /*rounding*/ + ) internal view virtual returns (uint256) { + return shares; + } + + /** + * @dev Deposit/mint common workflow. + */ + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the + // assets are transferred and before the shares are minted, which is a valid state. + // slither-disable-next-line reentrancy-no-eth + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _mint(receiver, shares); + + emit Deposit(caller, receiver, assets, shares); + } + + /** + * @dev Withdraw/redeem common workflow. + */ + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + + // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the + // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the + // shares are burned and after the assets are transferred, which is a valid state. + _burn(owner, shares); + SafeERC20.safeTransfer(_asset, receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + /** + * @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares. + */ + function _isVaultHealthy() private view returns (bool) { + return totalAssets() > 0 || totalSupply() == 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Metadata.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Metadata.sol new file mode 100644 index 0000000..9056e34 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Permit.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Permit.sol new file mode 100644 index 0000000..2370410 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/utils/SafeERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/utils/SafeERC20.sol new file mode 100644 index 0000000..fcdbbae --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/utils/SafeERC20.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; +import {IERC20Permit} from "../extensions/IERC20Permit.sol"; +import {Address} from "../../../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + /** + * @dev An operation with an ERC20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + */ + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); + } + + /** + * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 oldAllowance = token.allowance(address(this), spender); + forceApprove(token, spender, oldAllowance + value); + } + + /** + * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { + unchecked { + uint256 currentAllowance = token.allowance(address(this), spender); + if (currentAllowance < requestedDecrease) { + revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + forceApprove(token, spender, currentAllowance - requestedDecrease); + } + } + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. + */ + function forceApprove(IERC20 token, address spender, uint256 value) internal { + bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); + + if (!_callOptionalReturnBool(token, approvalCall)) { + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); + _callOptionalReturn(token, approvalCall); + } + } + + /** + * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. + * Revert on invalid signature. + */ + function safePermit( + IERC20Permit token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + uint256 nonceBefore = token.nonces(owner); + token.permit(owner, spender, value, deadline, v, r, s); + uint256 nonceAfter = token.nonces(owner); + if (nonceAfter != nonceBefore + 1) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data); + if (returndata.length != 0 && !abi.decode(returndata, (bool))) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false + // and not revert is the subcall reverts. + + (bool success, bytes memory returndata) = address(token).call(data); + return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Address.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Address.sol new file mode 100644 index 0000000..fd22b05 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Address.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error AddressInsufficientBalance(address account); + + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedInnerCall(); + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) { + revert AddressInsufficientBalance(address(this)); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert FailedInnerCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {FailedInnerCall} error. + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + if (address(this).balance < value) { + revert AddressInsufficientBalance(address(this)); + } + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an + * unsuccessful call. + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) internal view returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); + } + return returndata; + } + } + + /** + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {FailedInnerCall} error. + */ + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. + */ + function _revert(bytes memory returndata) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert FailedInnerCall(); + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Context.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Context.sol new file mode 100644 index 0000000..25e1159 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol new file mode 100644 index 0000000..17ce4c8 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) + * with further edits by Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. + // See https://cs.stackexchange.com/q/138556/92363. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + } + } + + /** + * @dev Return the log in base 2 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 10 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 256 of a positive value rounded towards zero. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + } + } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/IERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/IERC4626.sol new file mode 100644 index 0000000..e1b778e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + 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 + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/draft-IERC6093.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/draft-IERC6093.sol new file mode 100644 index 0000000..c38379a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/draft-IERC6093.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @dev Standard ERC20 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + */ +interface IERC20Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC20InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC20InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. + * @param spender Address that may be allowed to operate on tokens without being their owner. + * @param allowance Amount of tokens a `spender` is allowed to operate with. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC20InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `spender` to be approved. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC20InvalidSpender(address spender); +} + +/** + * @dev Standard ERC721 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + */ +interface IERC721Errors { + /** + * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. + * Used in balance queries. + * @param owner Address of the current owner of a token. + */ + error ERC721InvalidOwner(address owner); + + /** + * @dev Indicates a `tokenId` whose `owner` is the zero address. + * @param tokenId Identifier number of a token. + */ + error ERC721NonexistentToken(uint256 tokenId); + + /** + * @dev Indicates an error related to the ownership over a particular token. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param tokenId Identifier number of a token. + * @param owner Address of the current owner of a token. + */ + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC721InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC721InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param tokenId Identifier number of a token. + */ + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC721InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC721InvalidOperator(address operator); +} + +/** + * @dev Standard ERC1155 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. + */ +interface IERC1155Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + * @param tokenId Identifier number of a token. + */ + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC1155InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC1155InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param owner Address of the current owner of a token. + */ + error ERC1155MissingApprovalForAll(address operator, address owner); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC1155InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC1155InvalidOperator(address operator); + + /** + * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + * Used in batch transfers. + * @param idsLength Length of the array of token identifiers + * @param valuesLength Length of the array of token amounts + */ + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/DummyERC20.sol new file mode 100644 index 0000000..bc1376a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/DummyERC20.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract DummyERC20 is ERC20 { + constructor() ERC20("ERC20Mock2", "E20M2") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC20Mock.sol new file mode 100644 index 0000000..39ab129 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC20Mock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor() ERC20("ERC20Mock", "E20M") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC4626Mock.sol new file mode 100644 index 0000000..5ce832e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC4626Mock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +contract ERC4626Mock is ERC4626 { + constructor(address underlying) ERC4626(IERC20(underlying)) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/ERC20.sol new file mode 100644 index 0000000..8eeb314 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/ERC20.sol @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * The default value of {decimals} is 18. To change this, you should override + * this function so it returns a different value. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { + mapping(address account => uint256) private _balances; + + mapping(address account => mapping(address spender => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(address to, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, value); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, value); + _transfer(from, to, value); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `requestedDecrease`. + * + * NOTE: Although this function is designed to avoid double spending with {approval}, + * it can still be frontrunned, preventing any attempt of allowance reduction. + */ + function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance < requestedDecrease) { + revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + unchecked { + _approve(owner, spender, currentAllowance - requestedDecrease); + } + + return true; + } + + /** + * @dev Moves a `value` amount of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(address from, address to, uint256 value) internal { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(from, to, value); + } + + /** + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is + * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. + * + * Emits a {Transfer} event. + */ + function _update(address from, address to, uint256 value) internal virtual { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; + } else { + uint256 fromBalance = _balances[from]; + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[from] = fromBalance - value; + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[to] += value; + } + } + + emit Transfer(from, to, value); + } + + /** + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _mint(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * Relies on the `_update` mechanism. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead + */ + function _burn(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } + _update(account, address(0), value); + } + + /** + * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address owner, address spender, uint256 value) internal { + _approve(owner, spender, value, true); + } + + /** + * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. + * + * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by + * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any + * `Approval` event during `transferFrom` operations. + * + * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true + * using the following override: + * ``` + * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { + * super._approve(owner, spender, value, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _allowances[owner][spender] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/IERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/IERC20.sol new file mode 100644 index 0000000..77ca716 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/ERC4626.sol new file mode 100644 index 0000000..a8296fb --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/ERC4626.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; +import {IERC4626} from "../../../interfaces/IERC4626.sol"; +import {Math} from "../../../utils/math/Math.sol"; + +/** + * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in + * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + * + * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for + * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends + * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this + * contract and not the "assets" token which is an independent contract. + * + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial + * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * verifying the amount received is as expected, using a wrapper that performs these checks such as + * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + */ +contract ERC4626 is ERC20, IERC4626 { + using Math for uint256; + + IERC20 private immutable _asset; + uint8 private immutable _decimals; + + /** + * @dev Attempted to deposit more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @dev Attempted to mint more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @dev Attempted to withdraw more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @dev Attempted to redeem more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + + /** + * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + */ + constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _underlyingDecimals = success ? assetDecimals : 18; + _asset = asset_; + } + + /** + * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. + */ + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( + abi.encodeCall(IERC20Metadata.decimals, ()) + ); + if (success && encodedDecimals.length >= 32) { + uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + if (returnedDecimals <= type(uint8).max) { + return (true, uint8(returnedDecimals)); + } + } + return (false, 0); + } + + /** + * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This + * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the + * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + * + * See {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _decimals; + } + + /** @dev See {IERC4626-asset}. */ + function asset() public view virtual returns (address) { + return address(_asset); + } + + /** @dev See {IERC4626-totalAssets}. */ + function totalAssets() public view virtual returns (uint256) { + return _asset.balanceOf(address(this)); + } + + /** @dev See {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual returns (uint256) { + return _isVaultHealthy() ? type(uint256).max : 0; + } + + /** @dev See {IERC4626-maxMint}. */ + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf(owner); + } + + /** @dev See {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + + uint256 shares = previewDeposit(assets); + _deposit(_msgSender(), receiver, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-mint}. + * + * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. + * In this case, the shares will be minted without requiring any assets to be deposited. + */ + function mint(uint256 shares, address receiver) public virtual returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + + uint256 assets = previewMint(shares); + _deposit(_msgSender(), receiver, assets, shares); + + return assets; + } + + /** @dev See {IERC4626-withdraw}. */ + function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + + uint256 shares = previewWithdraw(assets); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-redeem}. */ + function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + + uint256 assets = previewRedeem(shares); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return assets; + } + + /** + * @dev Internal conversion function (from assets to shares) with support for rounding direction. + */ + function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { + uint256 supply = totalSupply(); + return + (assets == 0 || supply == 0) + ? _initialConvertToShares(assets, rounding) + : assets.mulDiv(supply, totalAssets(), rounding); + } + + /** + * @dev Internal conversion function (from assets to shares) to apply when the vault is empty. + * + * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it. + */ + function _initialConvertToShares( + uint256 assets, + Math.Rounding /*rounding*/ + ) internal view virtual returns (uint256 shares) { + return assets; + } + + /** + * @dev Internal conversion function (from shares to assets) with support for rounding direction. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + uint256 supply = totalSupply(); + return + (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding); + } + + /** + * @dev Internal conversion function (from shares to assets) to apply when the vault is empty. + * + * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it. + */ + function _initialConvertToAssets( + uint256 shares, + Math.Rounding /*rounding*/ + ) internal view virtual returns (uint256) { + return shares; + } + + /** + * @dev Deposit/mint common workflow. + */ + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the + // assets are transferred and before the shares are minted, which is a valid state. + // slither-disable-next-line reentrancy-no-eth + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _mint(receiver, shares); + + emit Deposit(caller, receiver, assets, shares); + } + + /** + * @dev Withdraw/redeem common workflow. + */ + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + + // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the + // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the + // shares are burned and after the assets are transferred, which is a valid state. + _burn(owner, shares); + SafeERC20.safeTransfer(_asset, receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + /** + * @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares. + */ + function _isVaultHealthy() private view returns (bool) { + return totalAssets() > 0 || totalSupply() == 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Metadata.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Metadata.sol new file mode 100644 index 0000000..9056e34 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Permit.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Permit.sol new file mode 100644 index 0000000..2370410 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Permit.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/utils/SafeERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/utils/SafeERC20.sol new file mode 100644 index 0000000..fcdbbae --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/utils/SafeERC20.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; +import {IERC20Permit} from "../extensions/IERC20Permit.sol"; +import {Address} from "../../../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + /** + * @dev An operation with an ERC20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + */ + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); + } + + /** + * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 oldAllowance = token.allowance(address(this), spender); + forceApprove(token, spender, oldAllowance + value); + } + + /** + * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { + unchecked { + uint256 currentAllowance = token.allowance(address(this), spender); + if (currentAllowance < requestedDecrease) { + revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + forceApprove(token, spender, currentAllowance - requestedDecrease); + } + } + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. + */ + function forceApprove(IERC20 token, address spender, uint256 value) internal { + bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); + + if (!_callOptionalReturnBool(token, approvalCall)) { + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); + _callOptionalReturn(token, approvalCall); + } + } + + /** + * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. + * Revert on invalid signature. + */ + function safePermit( + IERC20Permit token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + uint256 nonceBefore = token.nonces(owner); + token.permit(owner, spender, value, deadline, v, r, s); + uint256 nonceAfter = token.nonces(owner); + if (nonceAfter != nonceBefore + 1) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data); + if (returndata.length != 0 && !abi.decode(returndata, (bool))) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false + // and not revert is the subcall reverts. + + (bool success, bytes memory returndata) = address(token).call(data); + return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Address.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Address.sol new file mode 100644 index 0000000..fd22b05 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Address.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error AddressInsufficientBalance(address account); + + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedInnerCall(); + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) { + revert AddressInsufficientBalance(address(this)); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert FailedInnerCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {FailedInnerCall} error. + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + if (address(this).balance < value) { + revert AddressInsufficientBalance(address(this)); + } + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an + * unsuccessful call. + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) internal view returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); + } + return returndata; + } + } + + /** + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {FailedInnerCall} error. + */ + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. + */ + function _revert(bytes memory returndata) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert FailedInnerCall(); + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Context.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Context.sol new file mode 100644 index 0000000..25e1159 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/math/Math.sol new file mode 100644 index 0000000..17ce4c8 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/math/Math.sol @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) + * with further edits by Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. + // See https://cs.stackexchange.com/q/138556/92363. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + } + } + + /** + * @dev Return the log in base 2 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 10 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 256 of a positive value rounded towards zero. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + } + } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/IERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/IERC4626.sol new file mode 100644 index 0000000..e1b778e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + 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 + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/draft-IERC6093.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/draft-IERC6093.sol new file mode 100644 index 0000000..c38379a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/draft-IERC6093.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @dev Standard ERC20 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + */ +interface IERC20Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC20InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC20InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. + * @param spender Address that may be allowed to operate on tokens without being their owner. + * @param allowance Amount of tokens a `spender` is allowed to operate with. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC20InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `spender` to be approved. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC20InvalidSpender(address spender); +} + +/** + * @dev Standard ERC721 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + */ +interface IERC721Errors { + /** + * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. + * Used in balance queries. + * @param owner Address of the current owner of a token. + */ + error ERC721InvalidOwner(address owner); + + /** + * @dev Indicates a `tokenId` whose `owner` is the zero address. + * @param tokenId Identifier number of a token. + */ + error ERC721NonexistentToken(uint256 tokenId); + + /** + * @dev Indicates an error related to the ownership over a particular token. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param tokenId Identifier number of a token. + * @param owner Address of the current owner of a token. + */ + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC721InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC721InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param tokenId Identifier number of a token. + */ + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC721InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC721InvalidOperator(address operator); +} + +/** + * @dev Standard ERC1155 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. + */ +interface IERC1155Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + * @param tokenId Identifier number of a token. + */ + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC1155InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC1155InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param owner Address of the current owner of a token. + */ + error ERC1155MissingApprovalForAll(address operator, address owner); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC1155InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC1155InvalidOperator(address operator); + + /** + * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + * Used in batch transfers. + * @param idsLength Length of the array of token identifiers + * @param valuesLength Length of the array of token amounts + */ + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/DummyERC20.sol new file mode 100644 index 0000000..bc1376a --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/DummyERC20.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract DummyERC20 is ERC20 { + constructor() ERC20("ERC20Mock2", "E20M2") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC20Mock.sol new file mode 100644 index 0000000..39ab129 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC20Mock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor() ERC20("ERC20Mock", "E20M") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC4626Mock.sol new file mode 100644 index 0000000..5ce832e --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC4626Mock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +contract ERC4626Mock is ERC4626 { + constructor(address underlying) ERC4626(IERC20(underlying)) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/ERC20.sol new file mode 100644 index 0000000..8eeb314 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/ERC20.sol @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * The default value of {decimals} is 18. To change this, you should override + * this function so it returns a different value. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { + mapping(address account => uint256) private _balances; + + mapping(address account => mapping(address spender => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(address to, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, value); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, value); + _transfer(from, to, value); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `requestedDecrease`. + * + * NOTE: Although this function is designed to avoid double spending with {approval}, + * it can still be frontrunned, preventing any attempt of allowance reduction. + */ + function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance < requestedDecrease) { + revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + unchecked { + _approve(owner, spender, currentAllowance - requestedDecrease); + } + + return true; + } + + /** + * @dev Moves a `value` amount of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(address from, address to, uint256 value) internal { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(from, to, value); + } + + /** + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is + * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. + * + * Emits a {Transfer} event. + */ + function _update(address from, address to, uint256 value) internal virtual { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; + } else { + uint256 fromBalance = _balances[from]; + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[from] = fromBalance - value; + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[to] += value; + } + } + + emit Transfer(from, to, value); + } + + /** + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _mint(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * Relies on the `_update` mechanism. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead + */ + function _burn(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } + _update(account, address(0), value); + } + + /** + * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address owner, address spender, uint256 value) internal { + _approve(owner, spender, value, true); + } + + /** + * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. + * + * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by + * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any + * `Approval` event during `transferFrom` operations. + * + * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true + * using the following override: + * ``` + * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { + * super._approve(owner, spender, value, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _allowances[owner][spender] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/IERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/IERC20.sol new file mode 100644 index 0000000..77ca716 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/ERC4626.sol new file mode 100644 index 0000000..a8296fb --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/ERC4626.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; +import {IERC4626} from "../../../interfaces/IERC4626.sol"; +import {Math} from "../../../utils/math/Math.sol"; + +/** + * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in + * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + * + * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for + * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends + * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this + * contract and not the "assets" token which is an independent contract. + * + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial + * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * verifying the amount received is as expected, using a wrapper that performs these checks such as + * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + */ +contract ERC4626 is ERC20, IERC4626 { + using Math for uint256; + + IERC20 private immutable _asset; + uint8 private immutable _decimals; + + /** + * @dev Attempted to deposit more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @dev Attempted to mint more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @dev Attempted to withdraw more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @dev Attempted to redeem more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + + /** + * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + */ + constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _underlyingDecimals = success ? assetDecimals : 18; + _asset = asset_; + } + + /** + * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. + */ + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( + abi.encodeCall(IERC20Metadata.decimals, ()) + ); + if (success && encodedDecimals.length >= 32) { + uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + if (returnedDecimals <= type(uint8).max) { + return (true, uint8(returnedDecimals)); + } + } + return (false, 0); + } + + /** + * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This + * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the + * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + * + * See {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _decimals; + } + + /** @dev See {IERC4626-asset}. */ + function asset() public view virtual returns (address) { + return address(_asset); + } + + /** @dev See {IERC4626-totalAssets}. */ + function totalAssets() public view virtual returns (uint256) { + return _asset.balanceOf(address(this)); + } + + /** @dev See {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual returns (uint256) { + return _isVaultHealthy() ? type(uint256).max : 0; + } + + /** @dev See {IERC4626-maxMint}. */ + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf(owner); + } + + /** @dev See {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + + uint256 shares = previewDeposit(assets); + _deposit(_msgSender(), receiver, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-mint}. + * + * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. + * In this case, the shares will be minted without requiring any assets to be deposited. + */ + function mint(uint256 shares, address receiver) public virtual returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + + uint256 assets = previewMint(shares); + _deposit(_msgSender(), receiver, assets, shares); + + return assets; + } + + /** @dev See {IERC4626-withdraw}. */ + function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + + uint256 shares = previewWithdraw(assets); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-redeem}. */ + function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + + uint256 assets = previewRedeem(shares); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return assets; + } + + /** + * @dev Internal conversion function (from assets to shares) with support for rounding direction. + */ + function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { + uint256 supply = totalSupply(); + return + (assets == 0 || supply == 0) + ? _initialConvertToShares(assets, rounding) + : assets.mulDiv(supply, totalAssets(), rounding); + } + + /** + * @dev Internal conversion function (from assets to shares) to apply when the vault is empty. + * + * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it. + */ + function _initialConvertToShares( + uint256 assets, + Math.Rounding /*rounding*/ + ) internal view virtual returns (uint256 shares) { + return assets; + } + + /** + * @dev Internal conversion function (from shares to assets) with support for rounding direction. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + uint256 supply = totalSupply(); + return + (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding); + } + + /** + * @dev Internal conversion function (from shares to assets) to apply when the vault is empty. + * + * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it. + */ + function _initialConvertToAssets( + uint256 shares, + Math.Rounding /*rounding*/ + ) internal view virtual returns (uint256) { + return shares; + } + + /** + * @dev Deposit/mint common workflow. + */ + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the + // assets are transferred and before the shares are minted, which is a valid state. + // slither-disable-next-line reentrancy-no-eth + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _mint(receiver, shares); + + emit Deposit(caller, receiver, assets, shares); + } + + /** + * @dev Withdraw/redeem common workflow. + */ + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + + // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the + // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the + // shares are burned and after the assets are transferred, which is a valid state. + _burn(owner, shares); + SafeERC20.safeTransfer(_asset, receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + /** + * @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares. + */ + function _isVaultHealthy() private view returns (bool) { + return totalAssets() > 0 || totalSupply() == 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol new file mode 100644 index 0000000..9056e34 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol new file mode 100644 index 0000000..2370410 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/utils/SafeERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/utils/SafeERC20.sol new file mode 100644 index 0000000..fcdbbae --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/utils/SafeERC20.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; +import {IERC20Permit} from "../extensions/IERC20Permit.sol"; +import {Address} from "../../../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + /** + * @dev An operation with an ERC20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + */ + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); + } + + /** + * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 oldAllowance = token.allowance(address(this), spender); + forceApprove(token, spender, oldAllowance + value); + } + + /** + * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { + unchecked { + uint256 currentAllowance = token.allowance(address(this), spender); + if (currentAllowance < requestedDecrease) { + revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + forceApprove(token, spender, currentAllowance - requestedDecrease); + } + } + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. + */ + function forceApprove(IERC20 token, address spender, uint256 value) internal { + bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); + + if (!_callOptionalReturnBool(token, approvalCall)) { + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); + _callOptionalReturn(token, approvalCall); + } + } + + /** + * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. + * Revert on invalid signature. + */ + function safePermit( + IERC20Permit token, + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + uint256 nonceBefore = token.nonces(owner); + token.permit(owner, spender, value, deadline, v, r, s); + uint256 nonceAfter = token.nonces(owner); + if (nonceAfter != nonceBefore + 1) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data); + if (returndata.length != 0 && !abi.decode(returndata, (bool))) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false + // and not revert is the subcall reverts. + + (bool success, bytes memory returndata) = address(token).call(data); + return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Address.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Address.sol new file mode 100644 index 0000000..fd22b05 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Address.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error AddressInsufficientBalance(address account); + + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedInnerCall(); + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) { + revert AddressInsufficientBalance(address(this)); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert FailedInnerCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {FailedInnerCall} error. + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + if (address(this).balance < value) { + revert AddressInsufficientBalance(address(this)); + } + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an + * unsuccessful call. + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) internal view returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); + } + return returndata; + } + } + + /** + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {FailedInnerCall} error. + */ + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. + */ + function _revert(bytes memory returndata) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert FailedInnerCall(); + } + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Context.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Context.sol new file mode 100644 index 0000000..25e1159 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/math/Math.sol new file mode 100644 index 0000000..17ce4c8 --- /dev/null +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/math/Math.sol @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) + * with further edits by Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. + // See https://cs.stackexchange.com/q/138556/92363. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + } + } + + /** + * @dev Return the log in base 2 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 10 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 256 of a positive value rounded towards zero. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + } + } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } +} From aec6d79dacdc1c35b6a990706c03471ea623a92f Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Thu, 21 Sep 2023 09:06:21 +0200 Subject: [PATCH 115/125] Adding buggy version --- .../contracts copy/mocks/token/DummyERC20.sol | 16 - .../mocks/token/ERC4626Mock.sol | 17 - .../token/ERC20/extensions/ERC4626.sol | 319 -------------- .../contracts/mocks}/ERC20Mock.sol | 4 +- .../mocks/{token => }/ERC4626Mock.sol | 7 +- .../contracts/mocks/token/DummyERC20.sol | 16 - .../contracts/mocks/token/ERC20Mock.sol | 16 - .../token/ERC20/extensions/ERC4626.sol | 146 +++--- .../contracts/utils/math/Math.sol | 120 +---- .../contracts copy/interfaces/IERC4626.sol | 230 ---------- .../interfaces/draft-IERC6093.sol | 160 ------- .../contracts copy/mocks/token/DummyERC20.sol | 16 - .../contracts copy/token/ERC20/ERC20.sol | 372 ---------------- .../contracts copy/token/ERC20/IERC20.sol | 79 ---- .../token/ERC20/extensions/IERC20Metadata.sol | 26 -- .../token/ERC20/extensions/IERC20Permit.sol | 60 --- .../token/ERC20/utils/SafeERC20.sol | 140 ------ .../contracts copy/utils/Address.sol | 159 ------- .../contracts copy/utils/Context.sol | 24 - .../contracts copy/utils/math/Math.sol | 414 ------------------ .../contracts/interfaces/IERC4626.sol | 230 ---------- .../contracts/interfaces/draft-IERC6093.sol | 160 ------- .../contracts/mocks/token/DummyERC20.sol | 16 - .../contracts/mocks/token/ERC20Mock.sol | 16 - .../contracts/mocks/token/ERC4626Mock.sol | 17 - .../contracts/token/ERC20/ERC20.sol | 372 ---------------- .../contracts/token/ERC20/IERC20.sol | 79 ---- .../token/ERC20/extensions/ERC4626.sol | 319 -------------- .../token/ERC20/extensions/IERC20Metadata.sol | 26 -- .../token/ERC20/extensions/IERC20Permit.sol | 60 --- .../contracts/token/ERC20/utils/SafeERC20.sol | 140 ------ .../contracts/utils/Address.sol | 159 ------- .../contracts/utils/Context.sol | 24 - .../contracts/utils/math/Math.sol | 414 ------------------ .../contracts}/interfaces/IERC4626.sol | 0 .../contracts}/interfaces/draft-IERC6093.sol | 0 .../contracts/mocks}/ERC20Mock.sol | 4 +- .../contracts/mocks}/ERC4626Mock.sol | 7 +- .../contracts}/token/ERC20/ERC20.sol | 0 .../contracts}/token/ERC20/IERC20.sol | 0 .../token/ERC20/extensions/ERC4626.sol | 146 +++--- .../token/ERC20/extensions/IERC20Metadata.sol | 0 .../token/ERC20/extensions/IERC20Permit.sol | 0 .../token/ERC20/utils/SafeERC20.sol | 0 .../contracts}/utils/Address.sol | 0 .../contracts}/utils/Context.sol | 0 .../contracts}/utils/math/Math.sol | 120 +---- 47 files changed, 150 insertions(+), 4500 deletions(-) delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/DummyERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC4626Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/ERC4626.sol rename lesson4_reading/erc4626/src/{openzeppelin-d64d7a-fixed/contracts copy/mocks/token => openzeppelin-d5d9d4b-buggy/contracts/mocks}/ERC20Mock.sol (80%) rename lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/{token => }/ERC4626Mock.sol (55%) delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/DummyERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC20Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/IERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/draft-IERC6093.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/DummyERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/ERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/IERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Metadata.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Permit.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/utils/SafeERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Address.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Context.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/math/Math.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/IERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/draft-IERC6093.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/DummyERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC20Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC4626Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/ERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/IERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/ERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/utils/SafeERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Address.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Context.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/math/Math.sol rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/interfaces/IERC4626.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/interfaces/draft-IERC6093.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token => openzeppelin-d64d7aa-fixed/contracts/mocks}/ERC20Mock.sol (80%) rename lesson4_reading/erc4626/src/{openzeppelin-d64d7a-fixed/contracts copy/mocks/token => openzeppelin-d64d7aa-fixed/contracts/mocks}/ERC4626Mock.sol (55%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/token/ERC20/ERC20.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/token/ERC20/IERC20.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d64d7a-fixed/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/token/ERC20/extensions/ERC4626.sol (61%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/token/ERC20/extensions/IERC20Metadata.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/token/ERC20/extensions/IERC20Permit.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/token/ERC20/utils/SafeERC20.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/utils/Address.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/utils/Context.sol (100%) rename lesson4_reading/erc4626/src/{openzeppelin-d5d9d4b-buggy/contracts copy => openzeppelin-d64d7aa-fixed/contracts}/utils/math/Math.sol (74%) diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/DummyERC20.sol deleted file mode 100644 index bc1376a..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/DummyERC20.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "../../token/ERC20/ERC20.sol"; - -contract DummyERC20 is ERC20 { - constructor() ERC20("ERC20Mock2", "E20M2") {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC4626Mock.sol deleted file mode 100644 index 5ce832e..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC4626Mock.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; -import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; - -contract ERC4626Mock is ERC4626 { - constructor(address underlying) ERC4626(IERC20(underlying)) {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/ERC4626.sol deleted file mode 100644 index a8296fb..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/ERC4626.sol +++ /dev/null @@ -1,319 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) - -pragma solidity ^0.8.20; - -import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; -import {SafeERC20} from "../utils/SafeERC20.sol"; -import {IERC4626} from "../../../interfaces/IERC4626.sol"; -import {Math} from "../../../utils/math/Math.sol"; - -/** - * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in - * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. - * - * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for - * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends - * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this - * contract and not the "assets" token which is an independent contract. - * - * [CAUTION] - * ==== - * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning - * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation - * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial - * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may - * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by - * verifying the amount received is as expected, using a wrapper that performs these checks such as - * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. - * - * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` - * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault - * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself - * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset - * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's - * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more - * expensive than it is profitable. More details about the underlying math can be found - * xref:erc4626.adoc#inflation-attack[here]. - * - * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued - * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets - * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience - * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the - * `_convertToShares` and `_convertToAssets` functions. - * - * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. - * ==== - */ -contract ERC4626 is ERC20, IERC4626 { - using Math for uint256; - - IERC20 private immutable _asset; - uint8 private immutable _decimals; - - /** - * @dev Attempted to deposit more assets than the max amount for `receiver`. - */ - error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); - - /** - * @dev Attempted to mint more shares than the max amount for `receiver`. - */ - error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); - - /** - * @dev Attempted to withdraw more assets than the max amount for `receiver`. - */ - error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); - - /** - * @dev Attempted to redeem more shares than the max amount for `receiver`. - */ - error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - - /** - * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). - */ - constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { - (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); - _underlyingDecimals = success ? assetDecimals : 18; - _asset = asset_; - } - - /** - * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. - */ - function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { - (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( - abi.encodeCall(IERC20Metadata.decimals, ()) - ); - if (success && encodedDecimals.length >= 32) { - uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); - if (returnedDecimals <= type(uint8).max) { - return (true, uint8(returnedDecimals)); - } - } - return (false, 0); - } - - /** - * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This - * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the - * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. - * - * See {IERC20Metadata-decimals}. - */ - function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { - return _decimals; - } - - /** @dev See {IERC4626-asset}. */ - function asset() public view virtual returns (address) { - return address(_asset); - } - - /** @dev See {IERC4626-totalAssets}. */ - function totalAssets() public view virtual returns (uint256) { - return _asset.balanceOf(address(this)); - } - - /** @dev See {IERC4626-convertToShares}. */ - function convertToShares(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); - } - - /** @dev See {IERC4626-convertToAssets}. */ - function convertToAssets(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Floor); - } - - /** @dev See {IERC4626-maxDeposit}. */ - function maxDeposit(address) public view virtual returns (uint256) { - return _isVaultHealthy() ? type(uint256).max : 0; - } - - /** @dev See {IERC4626-maxMint}. */ - function maxMint(address) public view virtual returns (uint256) { - return type(uint256).max; - } - - /** @dev See {IERC4626-maxWithdraw}. */ - function maxWithdraw(address owner) public view virtual returns (uint256) { - return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); - } - - /** @dev See {IERC4626-maxRedeem}. */ - function maxRedeem(address owner) public view virtual returns (uint256) { - return balanceOf(owner); - } - - /** @dev See {IERC4626-previewDeposit}. */ - function previewDeposit(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); - } - - /** @dev See {IERC4626-previewMint}. */ - function previewMint(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Ceil); - } - - /** @dev See {IERC4626-previewWithdraw}. */ - function previewWithdraw(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Ceil); - } - - /** @dev See {IERC4626-previewRedeem}. */ - function previewRedeem(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Floor); - } - - /** @dev See {IERC4626-deposit}. */ - function deposit(uint256 assets, address receiver) public virtual returns (uint256) { - uint256 maxAssets = maxDeposit(receiver); - if (assets > maxAssets) { - revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); - } - - uint256 shares = previewDeposit(assets); - _deposit(_msgSender(), receiver, assets, shares); - - return shares; - } - - /** @dev See {IERC4626-mint}. - * - * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. - * In this case, the shares will be minted without requiring any assets to be deposited. - */ - function mint(uint256 shares, address receiver) public virtual returns (uint256) { - uint256 maxShares = maxMint(receiver); - if (shares > maxShares) { - revert ERC4626ExceededMaxMint(receiver, shares, maxShares); - } - - uint256 assets = previewMint(shares); - _deposit(_msgSender(), receiver, assets, shares); - - return assets; - } - - /** @dev See {IERC4626-withdraw}. */ - function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { - uint256 maxAssets = maxWithdraw(owner); - if (assets > maxAssets) { - revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); - } - - uint256 shares = previewWithdraw(assets); - _withdraw(_msgSender(), receiver, owner, assets, shares); - - return shares; - } - - /** @dev See {IERC4626-redeem}. */ - function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { - uint256 maxShares = maxRedeem(owner); - if (shares > maxShares) { - revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); - } - - uint256 assets = previewRedeem(shares); - _withdraw(_msgSender(), receiver, owner, assets, shares); - - return assets; - } - - /** - * @dev Internal conversion function (from assets to shares) with support for rounding direction. - */ - function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); - return - (assets == 0 || supply == 0) - ? _initialConvertToShares(assets, rounding) - : assets.mulDiv(supply, totalAssets(), rounding); - } - - /** - * @dev Internal conversion function (from assets to shares) to apply when the vault is empty. - * - * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it. - */ - function _initialConvertToShares( - uint256 assets, - Math.Rounding /*rounding*/ - ) internal view virtual returns (uint256 shares) { - return assets; - } - - /** - * @dev Internal conversion function (from shares to assets) with support for rounding direction. - */ - function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); - return - (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding); - } - - /** - * @dev Internal conversion function (from shares to assets) to apply when the vault is empty. - * - * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it. - */ - function _initialConvertToAssets( - uint256 shares, - Math.Rounding /*rounding*/ - ) internal view virtual returns (uint256) { - return shares; - } - - /** - * @dev Deposit/mint common workflow. - */ - function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { - // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the - // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, - // calls the vault, which is assumed not malicious. - // - // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the - // assets are transferred and before the shares are minted, which is a valid state. - // slither-disable-next-line reentrancy-no-eth - SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); - _mint(receiver, shares); - - emit Deposit(caller, receiver, assets, shares); - } - - /** - * @dev Withdraw/redeem common workflow. - */ - function _withdraw( - address caller, - address receiver, - address owner, - uint256 assets, - uint256 shares - ) internal virtual { - if (caller != owner) { - _spendAllowance(owner, caller, shares); - } - - // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the - // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, - // calls the vault, which is assumed not malicious. - // - // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the - // shares are burned and after the assets are transferred, which is a valid state. - _burn(owner, shares); - SafeERC20.safeTransfer(_asset, receiver, assets); - - emit Withdraw(caller, receiver, owner, assets, shares); - } - - /** - * @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares. - */ - function _isVaultHealthy() private view returns (bool) { - return totalAssets() > 0 || totalSupply() == 0; - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC20Mock.sol similarity index 80% rename from lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC20Mock.sol rename to lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC20Mock.sol index 39ab129..cc75923 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC20Mock.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC20Mock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; -import {ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC20} from "../token/ERC20/ERC20.sol"; contract ERC20Mock is ERC20 { constructor() ERC20("ERC20Mock", "E20M") {} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC4626Mock.sol similarity index 55% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC4626Mock.sol rename to lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC4626Mock.sol index 5ce832e..ef2d1a4 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC4626Mock.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC4626Mock.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; -import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; -import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; +import "../token/ERC20/extensions/ERC4626.sol"; contract ERC4626Mock is ERC4626 { - constructor(address underlying) ERC4626(IERC20(underlying)) {} + constructor(address underlying) ERC20("ERC4626Mock", "E4626M") ERC4626(IERC20(underlying)) {} function mint(address account, uint256 amount) external { _mint(account, amount); diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/DummyERC20.sol deleted file mode 100644 index bc1376a..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/DummyERC20.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "../../token/ERC20/ERC20.sol"; - -contract DummyERC20 is ERC20 { - constructor() ERC20("ERC20Mock2", "E20M2") {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC20Mock.sol deleted file mode 100644 index 39ab129..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/token/ERC20Mock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "../../token/ERC20/ERC20.sol"; - -contract ERC20Mock is ERC20 { - constructor() ERC20("ERC20Mock", "E20M") {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol index a8296fb..663377d 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) +// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC4626.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; -import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; -import {SafeERC20} from "../utils/SafeERC20.sol"; -import {IERC4626} from "../../../interfaces/IERC4626.sol"; -import {Math} from "../../../utils/math/Math.sol"; +import "../ERC20.sol"; +import "../utils/SafeERC20.sol"; +import "../../../interfaces/IERC4626.sol"; +import "../../../utils/math/Math.sol"; /** * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in @@ -17,66 +17,28 @@ import {Math} from "../../../utils/math/Math.sol"; * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this * contract and not the "assets" token which is an independent contract. * - * [CAUTION] - * ==== - * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning - * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * CAUTION: When the vault is empty or nearly empty, deposits are at high risk of being stolen through frontrunning with + * a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may - * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * similarly be affected by slippage. Users can protect against this attack as well unexpected slippage in general by * verifying the amount received is as expected, using a wrapper that performs these checks such as * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. * - * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` - * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault - * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself - * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset - * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's - * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more - * expensive than it is profitable. More details about the underlying math can be found - * xref:erc4626.adoc#inflation-attack[here]. - * - * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued - * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets - * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience - * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the - * `_convertToShares` and `_convertToAssets` functions. - * - * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. - * ==== + * _Available since v4.7._ */ -contract ERC4626 is ERC20, IERC4626 { +abstract contract ERC4626 is ERC20, IERC4626 { using Math for uint256; IERC20 private immutable _asset; uint8 private immutable _decimals; - /** - * @dev Attempted to deposit more assets than the max amount for `receiver`. - */ - error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); - - /** - * @dev Attempted to mint more shares than the max amount for `receiver`. - */ - error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); - - /** - * @dev Attempted to withdraw more assets than the max amount for `receiver`. - */ - error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); - - /** - * @dev Attempted to redeem more shares than the max amount for `receiver`. - */ - error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - /** * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ - constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { + constructor(IERC20 asset_) { (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); - _underlyingDecimals = success ? assetDecimals : 18; + _decimals = success ? assetDecimals : super.decimals(); _asset = asset_; } @@ -85,7 +47,7 @@ contract ERC4626 is ERC20, IERC4626 { */ function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( - abi.encodeCall(IERC20Metadata.decimals, ()) + abi.encodeWithSelector(IERC20Metadata.decimals.selector) ); if (success && encodedDecimals.length >= 32) { uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); @@ -97,10 +59,9 @@ contract ERC4626 is ERC20, IERC4626 { } /** - * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This - * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the - * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. - * + * @dev Decimals are read from the underlying asset in the constructor and cached. If this fails (e.g., the asset + * has not been created yet), the cached value is set to a default obtained by `super.decimals()` (which depends on + * inheritance but is most likely 18). Override this function in order to set a guaranteed hardcoded value. * See {IERC20Metadata-decimals}. */ function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { @@ -108,71 +69,68 @@ contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-asset}. */ - function asset() public view virtual returns (address) { + function asset() public view virtual override returns (address) { return address(_asset); } /** @dev See {IERC4626-totalAssets}. */ - function totalAssets() public view virtual returns (uint256) { + function totalAssets() public view virtual override returns (uint256) { return _asset.balanceOf(address(this)); } /** @dev See {IERC4626-convertToShares}. */ - function convertToShares(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); + function convertToShares(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Down); } /** @dev See {IERC4626-convertToAssets}. */ - function convertToAssets(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Floor); + function convertToAssets(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Down); } /** @dev See {IERC4626-maxDeposit}. */ - function maxDeposit(address) public view virtual returns (uint256) { + function maxDeposit(address) public view virtual override returns (uint256) { return _isVaultHealthy() ? type(uint256).max : 0; } /** @dev See {IERC4626-maxMint}. */ - function maxMint(address) public view virtual returns (uint256) { + function maxMint(address) public view virtual override returns (uint256) { return type(uint256).max; } /** @dev See {IERC4626-maxWithdraw}. */ - function maxWithdraw(address owner) public view virtual returns (uint256) { - return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + function maxWithdraw(address owner) public view virtual override returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Down); } /** @dev See {IERC4626-maxRedeem}. */ - function maxRedeem(address owner) public view virtual returns (uint256) { + function maxRedeem(address owner) public view virtual override returns (uint256) { return balanceOf(owner); } /** @dev See {IERC4626-previewDeposit}. */ - function previewDeposit(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); + function previewDeposit(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Down); } /** @dev See {IERC4626-previewMint}. */ - function previewMint(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Ceil); + function previewMint(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Up); } /** @dev See {IERC4626-previewWithdraw}. */ - function previewWithdraw(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Ceil); + function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Up); } /** @dev See {IERC4626-previewRedeem}. */ - function previewRedeem(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Floor); + function previewRedeem(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Down); } /** @dev See {IERC4626-deposit}. */ - function deposit(uint256 assets, address receiver) public virtual returns (uint256) { - uint256 maxAssets = maxDeposit(receiver); - if (assets > maxAssets) { - revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); - } + function deposit(uint256 assets, address receiver) public virtual override returns (uint256) { + require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max"); uint256 shares = previewDeposit(assets); _deposit(_msgSender(), receiver, assets, shares); @@ -185,11 +143,8 @@ contract ERC4626 is ERC20, IERC4626 { * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. * In this case, the shares will be minted without requiring any assets to be deposited. */ - function mint(uint256 shares, address receiver) public virtual returns (uint256) { - uint256 maxShares = maxMint(receiver); - if (shares > maxShares) { - revert ERC4626ExceededMaxMint(receiver, shares, maxShares); - } + function mint(uint256 shares, address receiver) public virtual override returns (uint256) { + require(shares <= maxMint(receiver), "ERC4626: mint more than max"); uint256 assets = previewMint(shares); _deposit(_msgSender(), receiver, assets, shares); @@ -198,11 +153,8 @@ contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-withdraw}. */ - function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { - uint256 maxAssets = maxWithdraw(owner); - if (assets > maxAssets) { - revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); - } + function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { + require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); uint256 shares = previewWithdraw(assets); _withdraw(_msgSender(), receiver, owner, assets, shares); @@ -211,11 +163,8 @@ contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-redeem}. */ - function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { - uint256 maxShares = maxRedeem(owner); - if (shares > maxShares) { - revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); - } + function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { + require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); uint256 assets = previewRedeem(shares); _withdraw(_msgSender(), receiver, owner, assets, shares); @@ -225,9 +174,12 @@ contract ERC4626 is ERC20, IERC4626 { /** * @dev Internal conversion function (from assets to shares) with support for rounding direction. + * + * Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset + * would represent an infinite amount of shares. */ function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); + uint256 supply = totalSupply(); return (assets == 0 || supply == 0) ? _initialConvertToShares(assets, rounding) @@ -271,7 +223,7 @@ contract ERC4626 is ERC20, IERC4626 { * @dev Deposit/mint common workflow. */ function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { - // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, // calls the vault, which is assumed not malicious. // diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol index 17ce4c8..8400d06 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol @@ -1,78 +1,16 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) +// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { - /** - * @dev Muldiv operation overflow. - */ - error MathOverflowedMulDiv(); - enum Rounding { - Floor, // Toward negative infinity - Ceil, // Toward positive infinity - Trunc, // Toward zero - Expand // Away from zero - } - - /** - * @dev Returns the addition of two unsigned integers, with an overflow flag. - */ - function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - uint256 c = a + b; - if (c < a) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the subtraction of two unsigned integers, with an overflow flag. - */ - function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b > a) return (false, 0); - return (true, a - b); - } - } - - /** - * @dev Returns the multiplication of two unsigned integers, with an overflow flag. - */ - function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 - if (a == 0) return (true, 0); - uint256 c = a * b; - if (c / a != b) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the division of two unsigned integers, with a division by zero flag. - */ - function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a / b); - } - } - - /** - * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. - */ - function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a % b); - } + Down, // Toward negative infinity + Up, // Toward infinity + Zero // Toward zero } /** @@ -101,15 +39,10 @@ library Math { /** * @dev Returns the ceiling of the division of two numbers. * - * This differs from standard division with `/` in that it rounds towards infinity instead - * of rounding towards zero. + * This differs from standard division with `/` in that it rounds up instead + * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { - if (b == 0) { - // Guarantee the same behavior as in a regular Solidity division. - return a / b; - } - // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } @@ -124,25 +57,21 @@ library Math { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. - uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) + prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { - // Solidity will revert if denominator == 0, unlike the div opcode on its own. - // The surrounding unchecked block does not change this fact. - // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. - if (denominator <= prod1) { - revert MathOverflowedMulDiv(); - } + require(denominator > prod1, "Math: mulDiv overflow"); /////////////////////////////////////////////// // 512 by 256 division. @@ -162,7 +91,8 @@ library Math { // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. - uint256 twos = denominator & (0 - denominator); + // Does not overflow because the denominator cannot be zero at this stage in the function. + uint256 twos = denominator & (~denominator + 1); assembly { // Divide denominator by twos. denominator := div(denominator, twos) @@ -205,15 +135,14 @@ library Math { */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); - if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** - * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded - * towards zero. + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ @@ -256,12 +185,12 @@ library Math { function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); - return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); } } /** - * @dev Return the log in base 2 of a positive value rounded towards zero. + * @dev Return the log in base 2, rounded down, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { @@ -309,12 +238,12 @@ library Math { function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); - return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); } } /** - * @dev Return the log in base 10 of a positive value rounded towards zero. + * @dev Return the log in base 10, rounded down, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { @@ -358,12 +287,12 @@ library Math { function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); - return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); } } /** - * @dev Return the log in base 256 of a positive value rounded towards zero. + * @dev Return the log in base 256, rounded down, of a positive value. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. @@ -401,14 +330,7 @@ library Math { function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); - return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); } } - - /** - * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. - */ - function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { - return uint8(rounding) % 2 == 1; - } } diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/IERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/IERC4626.sol deleted file mode 100644 index e1b778e..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/IERC4626.sol +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) - -pragma solidity ^0.8.20; - -import {IERC20} from "../token/ERC20/IERC20.sol"; -import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; - -/** - * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in - * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. - */ -interface IERC4626 is IERC20, IERC20Metadata { - 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 - ); - - /** - * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. - * - * - MUST be an ERC-20 token contract. - * - MUST NOT revert. - */ - function asset() external view returns (address assetTokenAddress); - - /** - * @dev Returns the total amount of the underlying asset that is “managed” by Vault. - * - * - SHOULD include any compounding that occurs from yield. - * - MUST be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT revert. - */ - function totalAssets() external view returns (uint256 totalManagedAssets); - - /** - * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal - * scenario where all the conditions are met. - * - * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT show any variations depending on the caller. - * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - * - MUST NOT revert. - * - * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the - * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and - * from. - */ - function convertToShares(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal - * scenario where all the conditions are met. - * - * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT show any variations depending on the caller. - * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - * - MUST NOT revert. - * - * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the - * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and - * from. - */ - function convertToAssets(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, - * through a deposit call. - * - * - MUST return a limited value if receiver is subject to some deposit limit. - * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. - * - MUST NOT revert. - */ - function maxDeposit(address receiver) external view returns (uint256 maxAssets); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given - * current on-chain conditions. - * - * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit - * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called - * in the same transaction. - * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the - * deposit would be accepted, regardless if the user has enough tokens approved, etc. - * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by depositing. - */ - function previewDeposit(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. - * - * - MUST emit the Deposit event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * deposit execution, and are accounted for during deposit. - * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not - * approving enough underlying tokens to the Vault contract, etc). - * - * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. - */ - function deposit(uint256 assets, address receiver) external returns (uint256 shares); - - /** - * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. - * - MUST return a limited value if receiver is subject to some mint limit. - * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. - * - MUST NOT revert. - */ - function maxMint(address receiver) external view returns (uint256 maxShares); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given - * current on-chain conditions. - * - * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call - * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the - * same transaction. - * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint - * would be accepted, regardless if the user has enough tokens approved, etc. - * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by minting. - */ - function previewMint(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. - * - * - MUST emit the Deposit event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint - * execution, and are accounted for during mint. - * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not - * approving enough underlying tokens to the Vault contract, etc). - * - * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. - */ - function mint(uint256 shares, address receiver) external returns (uint256 assets); - - /** - * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the - * Vault, through a withdraw call. - * - * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. - * - MUST NOT revert. - */ - function maxWithdraw(address owner) external view returns (uint256 maxAssets); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, - * given current on-chain conditions. - * - * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw - * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if - * called - * in the same transaction. - * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though - * the withdrawal would be accepted, regardless if the user has enough shares, etc. - * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by depositing. - */ - function previewWithdraw(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. - * - * - MUST emit the Withdraw event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * withdraw execution, and are accounted for during withdraw. - * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner - * not having enough shares, etc). - * - * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. - * Those methods should be performed separately. - */ - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); - - /** - * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, - * through a redeem call. - * - * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. - * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. - * - MUST NOT revert. - */ - function maxRedeem(address owner) external view returns (uint256 maxShares); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, - * given current on-chain conditions. - * - * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call - * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the - * same transaction. - * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the - * redemption would be accepted, regardless if the user has enough shares, etc. - * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by redeeming. - */ - function previewRedeem(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. - * - * - MUST emit the Withdraw event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * redeem execution, and are accounted for during redeem. - * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner - * not having enough shares, etc). - * - * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. - * Those methods should be performed separately. - */ - function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/draft-IERC6093.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/draft-IERC6093.sol deleted file mode 100644 index c38379a..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/interfaces/draft-IERC6093.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -/** - * @dev Standard ERC20 Errors - * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. - */ -interface IERC20Errors { - /** - * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - * @param balance Current balance for the interacting account. - * @param needed Minimum amount required to perform a transfer. - */ - error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); - - /** - * @dev Indicates a failure with the token `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - */ - error ERC20InvalidSender(address sender); - - /** - * @dev Indicates a failure with the token `receiver`. Used in transfers. - * @param receiver Address to which tokens are being transferred. - */ - error ERC20InvalidReceiver(address receiver); - - /** - * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. - * @param spender Address that may be allowed to operate on tokens without being their owner. - * @param allowance Amount of tokens a `spender` is allowed to operate with. - * @param needed Minimum amount required to perform a transfer. - */ - error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); - - /** - * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. - * @param approver Address initiating an approval operation. - */ - error ERC20InvalidApprover(address approver); - - /** - * @dev Indicates a failure with the `spender` to be approved. Used in approvals. - * @param spender Address that may be allowed to operate on tokens without being their owner. - */ - error ERC20InvalidSpender(address spender); -} - -/** - * @dev Standard ERC721 Errors - * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. - */ -interface IERC721Errors { - /** - * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. - * Used in balance queries. - * @param owner Address of the current owner of a token. - */ - error ERC721InvalidOwner(address owner); - - /** - * @dev Indicates a `tokenId` whose `owner` is the zero address. - * @param tokenId Identifier number of a token. - */ - error ERC721NonexistentToken(uint256 tokenId); - - /** - * @dev Indicates an error related to the ownership over a particular token. Used in transfers. - * @param sender Address whose tokens are being transferred. - * @param tokenId Identifier number of a token. - * @param owner Address of the current owner of a token. - */ - error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); - - /** - * @dev Indicates a failure with the token `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - */ - error ERC721InvalidSender(address sender); - - /** - * @dev Indicates a failure with the token `receiver`. Used in transfers. - * @param receiver Address to which tokens are being transferred. - */ - error ERC721InvalidReceiver(address receiver); - - /** - * @dev Indicates a failure with the `operator`’s approval. Used in transfers. - * @param operator Address that may be allowed to operate on tokens without being their owner. - * @param tokenId Identifier number of a token. - */ - error ERC721InsufficientApproval(address operator, uint256 tokenId); - - /** - * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. - * @param approver Address initiating an approval operation. - */ - error ERC721InvalidApprover(address approver); - - /** - * @dev Indicates a failure with the `operator` to be approved. Used in approvals. - * @param operator Address that may be allowed to operate on tokens without being their owner. - */ - error ERC721InvalidOperator(address operator); -} - -/** - * @dev Standard ERC1155 Errors - * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. - */ -interface IERC1155Errors { - /** - * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - * @param balance Current balance for the interacting account. - * @param needed Minimum amount required to perform a transfer. - * @param tokenId Identifier number of a token. - */ - error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); - - /** - * @dev Indicates a failure with the token `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - */ - error ERC1155InvalidSender(address sender); - - /** - * @dev Indicates a failure with the token `receiver`. Used in transfers. - * @param receiver Address to which tokens are being transferred. - */ - error ERC1155InvalidReceiver(address receiver); - - /** - * @dev Indicates a failure with the `operator`’s approval. Used in transfers. - * @param operator Address that may be allowed to operate on tokens without being their owner. - * @param owner Address of the current owner of a token. - */ - error ERC1155MissingApprovalForAll(address operator, address owner); - - /** - * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. - * @param approver Address initiating an approval operation. - */ - error ERC1155InvalidApprover(address approver); - - /** - * @dev Indicates a failure with the `operator` to be approved. Used in approvals. - * @param operator Address that may be allowed to operate on tokens without being their owner. - */ - error ERC1155InvalidOperator(address operator); - - /** - * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. - * Used in batch transfers. - * @param idsLength Length of the array of token identifiers - * @param valuesLength Length of the array of token amounts - */ - error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/DummyERC20.sol deleted file mode 100644 index bc1376a..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/DummyERC20.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "../../token/ERC20/ERC20.sol"; - -contract DummyERC20 is ERC20 { - constructor() ERC20("ERC20Mock2", "E20M2") {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/ERC20.sol deleted file mode 100644 index 8eeb314..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/ERC20.sol +++ /dev/null @@ -1,372 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) - -pragma solidity ^0.8.20; - -import {IERC20} from "./IERC20.sol"; -import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; -import {Context} from "../../utils/Context.sol"; -import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; - -/** - * @dev Implementation of the {IERC20} interface. - * - * This implementation is agnostic to the way tokens are created. This means - * that a supply mechanism has to be added in a derived contract using {_mint}. - * - * TIP: For a detailed writeup see our guide - * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * The default value of {decimals} is 18. To change this, you should override - * this function so it returns a different value. - * - * We have followed general OpenZeppelin Contracts guidelines: functions revert - * instead returning `false` on failure. This behavior is nonetheless - * conventional and does not conflict with the expectations of ERC20 - * applications. - * - * Additionally, an {Approval} event is emitted on calls to {transferFrom}. - * This allows applications to reconstruct the allowance for all accounts just - * by listening to said events. Other implementations of the EIP may not emit - * these events, as it isn't required by the specification. - * - * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} - * functions have been added to mitigate the well-known issues around setting - * allowances. See {IERC20-approve}. - */ -abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { - mapping(address account => uint256) private _balances; - - mapping(address account => mapping(address spender => uint256)) private _allowances; - - uint256 private _totalSupply; - - string private _name; - string private _symbol; - - /** - * @dev Indicates a failed `decreaseAllowance` request. - */ - error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); - - /** - * @dev Sets the values for {name} and {symbol}. - * - * All two of these values are immutable: they can only be set once during - * construction. - */ - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5.05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the default value returned by this function, unless - * it's overridden. - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view virtual returns (uint8) { - return 18; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view virtual returns (uint256) { - return _totalSupply; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual returns (uint256) { - return _balances[account]; - } - - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `value`. - */ - function transfer(address to, uint256 value) public virtual returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, value); - return true; - } - - /** - * @dev See {IERC20-allowance}. - */ - function allowance(address owner, address spender) public view virtual returns (uint256) { - return _allowances[owner][spender]; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 value) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, value); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `value`. - * - the caller must have allowance for ``from``'s tokens of at least - * `value`. - */ - function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, value); - _transfer(from, to, value); - return true; - } - - /** - * @dev Atomically increases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, allowance(owner, spender) + addedValue); - return true; - } - - /** - * @dev Atomically decreases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `spender` must have allowance for the caller of at least - * `requestedDecrease`. - * - * NOTE: Although this function is designed to avoid double spending with {approval}, - * it can still be frontrunned, preventing any attempt of allowance reduction. - */ - function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { - address owner = _msgSender(); - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance < requestedDecrease) { - revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); - } - unchecked { - _approve(owner, spender, currentAllowance - requestedDecrease); - } - - return true; - } - - /** - * @dev Moves a `value` amount of tokens from `from` to `to`. - * - * This internal function is equivalent to {transfer}, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a {Transfer} event. - * - * NOTE: This function is not virtual, {_update} should be overridden instead. - */ - function _transfer(address from, address to, uint256 value) internal { - if (from == address(0)) { - revert ERC20InvalidSender(address(0)); - } - if (to == address(0)) { - revert ERC20InvalidReceiver(address(0)); - } - _update(from, to, value); - } - - /** - * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is - * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. - * - * Emits a {Transfer} event. - */ - function _update(address from, address to, uint256 value) internal virtual { - if (from == address(0)) { - // Overflow check required: The rest of the code assumes that totalSupply never overflows - _totalSupply += value; - } else { - uint256 fromBalance = _balances[from]; - if (fromBalance < value) { - revert ERC20InsufficientBalance(from, fromBalance, value); - } - unchecked { - // Overflow not possible: value <= fromBalance <= totalSupply. - _balances[from] = fromBalance - value; - } - } - - if (to == address(0)) { - unchecked { - // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. - _totalSupply -= value; - } - } else { - unchecked { - // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. - _balances[to] += value; - } - } - - emit Transfer(from, to, value); - } - - /** - * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). - * Relies on the `_update` mechanism - * - * Emits a {Transfer} event with `from` set to the zero address. - * - * NOTE: This function is not virtual, {_update} should be overridden instead. - */ - function _mint(address account, uint256 value) internal { - if (account == address(0)) { - revert ERC20InvalidReceiver(address(0)); - } - _update(address(0), account, value); - } - - /** - * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. - * Relies on the `_update` mechanism. - * - * Emits a {Transfer} event with `to` set to the zero address. - * - * NOTE: This function is not virtual, {_update} should be overridden instead - */ - function _burn(address account, uint256 value) internal { - if (account == address(0)) { - revert ERC20InvalidSender(address(0)); - } - _update(account, address(0), value); - } - - /** - * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. - * - * This internal function is equivalent to `approve`, and can be used to - * e.g. set automatic allowances for certain subsystems, etc. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `owner` cannot be the zero address. - * - `spender` cannot be the zero address. - * - * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. - */ - function _approve(address owner, address spender, uint256 value) internal { - _approve(owner, spender, value, true); - } - - /** - * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. - * - * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by - * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any - * `Approval` event during `transferFrom` operations. - * - * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true - * using the following override: - * ``` - * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { - * super._approve(owner, spender, value, true); - * } - * ``` - * - * Requirements are the same as {_approve}. - */ - function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { - if (owner == address(0)) { - revert ERC20InvalidApprover(address(0)); - } - if (spender == address(0)) { - revert ERC20InvalidSpender(address(0)); - } - _allowances[owner][spender] = value; - if (emitEvent) { - emit Approval(owner, spender, value); - } - } - - /** - * @dev Updates `owner` s allowance for `spender` based on spent `value`. - * - * Does not update the allowance value in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance(address owner, address spender, uint256 value) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - if (currentAllowance < value) { - revert ERC20InsufficientAllowance(spender, currentAllowance, value); - } - unchecked { - _approve(owner, spender, currentAllowance - value, false); - } - } - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/IERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/IERC20.sol deleted file mode 100644 index 77ca716..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/IERC20.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Interface of the ERC20 standard as defined in the EIP. - */ -interface IERC20 { - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); - - /** - * @dev Returns the value of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the value of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves a `value` amount of tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address to, uint256 value) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets a `value` amount of tokens as the allowance of `spender` over the - * caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 value) external returns (bool); - - /** - * @dev Moves a `value` amount of tokens from `from` to `to` using the - * allowance mechanism. `value` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom(address from, address to, uint256 value) external returns (bool); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Metadata.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Metadata.sol deleted file mode 100644 index 9056e34..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Metadata.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) - -pragma solidity ^0.8.20; - -import {IERC20} from "../IERC20.sol"; - -/** - * @dev Interface for the optional metadata functions from the ERC20 standard. - */ -interface IERC20Metadata is IERC20 { - /** - * @dev Returns the name of the token. - */ - function name() external view returns (string memory); - - /** - * @dev Returns the symbol of the token. - */ - function symbol() external view returns (string memory); - - /** - * @dev Returns the decimals places of the token. - */ - function decimals() external view returns (uint8); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Permit.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Permit.sol deleted file mode 100644 index 2370410..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/IERC20Permit.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in - * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - * - * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by - * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't - * need to send a transaction, and thus is not required to hold Ether at all. - */ -interface IERC20Permit { - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * - * IMPORTANT: The same issues {IERC20-approve} has related to transaction - * ordering also apply here. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - * - * For more information on the signature format, see the - * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP - * section]. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256); - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/utils/SafeERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/utils/SafeERC20.sol deleted file mode 100644 index fcdbbae..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/utils/SafeERC20.sol +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) - -pragma solidity ^0.8.20; - -import {IERC20} from "../IERC20.sol"; -import {IERC20Permit} from "../extensions/IERC20Permit.sol"; -import {Address} from "../../../utils/Address.sol"; - -/** - * @title SafeERC20 - * @dev Wrappers around ERC20 operations that throw on failure (when the token - * contract returns false). Tokens that return no value (and instead revert or - * throw on failure) are also supported, non-reverting calls are assumed to be - * successful. - * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, - * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. - */ -library SafeERC20 { - using Address for address; - - /** - * @dev An operation with an ERC20 token failed. - */ - error SafeERC20FailedOperation(address token); - - /** - * @dev Indicates a failed `decreaseAllowance` request. - */ - error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); - - /** - * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeTransfer(IERC20 token, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); - } - - /** - * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the - * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. - */ - function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); - } - - /** - * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 oldAllowance = token.allowance(address(this), spender); - forceApprove(token, spender, oldAllowance + value); - } - - /** - * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { - unchecked { - uint256 currentAllowance = token.allowance(address(this), spender); - if (currentAllowance < requestedDecrease) { - revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); - } - forceApprove(token, spender, currentAllowance - requestedDecrease); - } - } - - /** - * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, - * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval - * to be set to zero before setting it to a non-zero value, such as USDT. - */ - function forceApprove(IERC20 token, address spender, uint256 value) internal { - bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); - - if (!_callOptionalReturnBool(token, approvalCall)) { - _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); - _callOptionalReturn(token, approvalCall); - } - } - - /** - * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. - * Revert on invalid signature. - */ - function safePermit( - IERC20Permit token, - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) internal { - uint256 nonceBefore = token.nonces(owner); - token.permit(owner, spender, value, deadline, v, r, s); - uint256 nonceAfter = token.nonces(owner); - if (nonceAfter != nonceBefore + 1) { - revert SafeERC20FailedOperation(address(token)); - } - } - - /** - * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - * on the return value: the return value is optional (but if data is returned, it must not be false). - * @param token The token targeted by the call. - * @param data The call data (encoded using abi.encode or one of its variants). - */ - function _callOptionalReturn(IERC20 token, bytes memory data) private { - // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since - // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that - // the target address contains contract code and also asserts for success in the low-level call. - - bytes memory returndata = address(token).functionCall(data); - if (returndata.length != 0 && !abi.decode(returndata, (bool))) { - revert SafeERC20FailedOperation(address(token)); - } - } - - /** - * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - * on the return value: the return value is optional (but if data is returned, it must not be false). - * @param token The token targeted by the call. - * @param data The call data (encoded using abi.encode or one of its variants). - * - * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. - */ - function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { - // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since - // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false - // and not revert is the subcall reverts. - - (bool success, bytes memory returndata) = address(token).call(data); - return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Address.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Address.sol deleted file mode 100644 index fd22b05..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Address.sol +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev The ETH balance of the account is not enough to perform the operation. - */ - error AddressInsufficientBalance(address account); - - /** - * @dev There's no code at `target` (it is not a contract). - */ - error AddressEmptyCode(address target); - - /** - * @dev A call to an address target failed. The target may have reverted. - */ - error FailedInnerCall(); - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - if (address(this).balance < amount) { - revert AddressInsufficientBalance(address(this)); - } - - (bool success, ) = recipient.call{value: amount}(""); - if (!success) { - revert FailedInnerCall(); - } - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason or custom error, it is bubbled - * up by this function (like regular Solidity function calls). However, if - * the call reverted with no returned reason, this function reverts with a - * {FailedInnerCall} error. - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - */ - function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { - if (address(this).balance < value) { - revert AddressInsufficientBalance(address(this)); - } - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResultFromTarget(target, success, returndata); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResultFromTarget(target, success, returndata); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResultFromTarget(target, success, returndata); - } - - /** - * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target - * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an - * unsuccessful call. - */ - function verifyCallResultFromTarget( - address target, - bool success, - bytes memory returndata - ) internal view returns (bytes memory) { - if (!success) { - _revert(returndata); - } else { - // only check if target is a contract if the call was successful and the return data is empty - // otherwise we already know that it was a contract - if (returndata.length == 0 && target.code.length == 0) { - revert AddressEmptyCode(target); - } - return returndata; - } - } - - /** - * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the - * revert reason or with a default {FailedInnerCall} error. - */ - function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { - if (!success) { - _revert(returndata); - } else { - return returndata; - } - } - - /** - * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. - */ - function _revert(bytes memory returndata) private pure { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert FailedInnerCall(); - } - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Context.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Context.sol deleted file mode 100644 index 25e1159..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/Context.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Provides information about the current execution context, including the - * sender of the transaction and its data. While these are generally available - * via msg.sender and msg.data, they should not be accessed in such a direct - * manner, since when dealing with meta-transactions the account sending and - * paying for execution may not be the actual sender (as far as an application - * is concerned). - * - * This contract is only required for intermediate, library-like contracts. - */ -abstract contract Context { - function _msgSender() internal view virtual returns (address) { - return msg.sender; - } - - function _msgData() internal view virtual returns (bytes calldata) { - return msg.data; - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/math/Math.sol deleted file mode 100644 index 17ce4c8..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/utils/math/Math.sol +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Standard math utilities missing in the Solidity language. - */ -library Math { - /** - * @dev Muldiv operation overflow. - */ - error MathOverflowedMulDiv(); - - enum Rounding { - Floor, // Toward negative infinity - Ceil, // Toward positive infinity - Trunc, // Toward zero - Expand // Away from zero - } - - /** - * @dev Returns the addition of two unsigned integers, with an overflow flag. - */ - function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - uint256 c = a + b; - if (c < a) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the subtraction of two unsigned integers, with an overflow flag. - */ - function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b > a) return (false, 0); - return (true, a - b); - } - } - - /** - * @dev Returns the multiplication of two unsigned integers, with an overflow flag. - */ - function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 - if (a == 0) return (true, 0); - uint256 c = a * b; - if (c / a != b) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the division of two unsigned integers, with a division by zero flag. - */ - function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a / b); - } - } - - /** - * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. - */ - function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a % b); - } - } - - /** - * @dev Returns the largest of two numbers. - */ - function max(uint256 a, uint256 b) internal pure returns (uint256) { - return a > b ? a : b; - } - - /** - * @dev Returns the smallest of two numbers. - */ - function min(uint256 a, uint256 b) internal pure returns (uint256) { - return a < b ? a : b; - } - - /** - * @dev Returns the average of two numbers. The result is rounded towards - * zero. - */ - function average(uint256 a, uint256 b) internal pure returns (uint256) { - // (a + b) / 2 can overflow. - return (a & b) + (a ^ b) / 2; - } - - /** - * @dev Returns the ceiling of the division of two numbers. - * - * This differs from standard division with `/` in that it rounds towards infinity instead - * of rounding towards zero. - */ - function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { - if (b == 0) { - // Guarantee the same behavior as in a regular Solidity division. - return a / b; - } - - // (a + b - 1) / b can overflow on addition, so we distribute. - return a == 0 ? 0 : (a - 1) / b + 1; - } - - /** - * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 - * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) - * with further edits by Uniswap Labs also under MIT license. - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { - unchecked { - // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use - // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 - // variables such that product = prod1 * 2^256 + prod0. - uint256 prod0 = x * y; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(x, y, not(0)) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - // Handle non-overflow cases, 256 by 256 division. - if (prod1 == 0) { - // Solidity will revert if denominator == 0, unlike the div opcode on its own. - // The surrounding unchecked block does not change this fact. - // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. - return prod0 / denominator; - } - - // Make sure the result is less than 2^256. Also prevents denominator == 0. - if (denominator <= prod1) { - revert MathOverflowedMulDiv(); - } - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0]. - uint256 remainder; - assembly { - // Compute remainder using mulmod. - remainder := mulmod(x, y, denominator) - - // Subtract 256 bit number from 512 bit number. - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. - // See https://cs.stackexchange.com/q/138556/92363. - - uint256 twos = denominator & (0 - denominator); - assembly { - // Divide denominator by twos. - denominator := div(denominator, twos) - - // Divide [prod1 prod0] by twos. - prod0 := div(prod0, twos) - - // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. - twos := add(div(sub(0, twos), twos), 1) - } - - // Shift in bits from prod1 into prod0. - prod0 |= prod1 * twos; - - // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such - // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for - // four bits. That is, denominator * inv = 1 mod 2^4. - uint256 inverse = (3 * denominator) ^ 2; - - // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works - // in modular arithmetic, doubling the correct bits in each step. - inverse *= 2 - denominator * inverse; // inverse mod 2^8 - inverse *= 2 - denominator * inverse; // inverse mod 2^16 - inverse *= 2 - denominator * inverse; // inverse mod 2^32 - inverse *= 2 - denominator * inverse; // inverse mod 2^64 - inverse *= 2 - denominator * inverse; // inverse mod 2^128 - inverse *= 2 - denominator * inverse; // inverse mod 2^256 - - // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. - // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is - // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 - // is no longer required. - result = prod0 * inverse; - return result; - } - } - - /** - * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { - uint256 result = mulDiv(x, y, denominator); - if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { - result += 1; - } - return result; - } - - /** - * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded - * towards zero. - * - * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). - */ - function sqrt(uint256 a) internal pure returns (uint256) { - if (a == 0) { - return 0; - } - - // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. - // - // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have - // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. - // - // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` - // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` - // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` - // - // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. - uint256 result = 1 << (log2(a) >> 1); - - // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, - // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at - // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision - // into the expected uint128 result. - unchecked { - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - return min(result, a / result); - } - } - - /** - * @notice Calculates sqrt(a), following the selected rounding direction. - */ - function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = sqrt(a); - return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); - } - } - - /** - * @dev Return the log in base 2 of a positive value rounded towards zero. - * Returns 0 if given 0. - */ - function log2(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >> 128 > 0) { - value >>= 128; - result += 128; - } - if (value >> 64 > 0) { - value >>= 64; - result += 64; - } - if (value >> 32 > 0) { - value >>= 32; - result += 32; - } - if (value >> 16 > 0) { - value >>= 16; - result += 16; - } - if (value >> 8 > 0) { - value >>= 8; - result += 8; - } - if (value >> 4 > 0) { - value >>= 4; - result += 4; - } - if (value >> 2 > 0) { - value >>= 2; - result += 2; - } - if (value >> 1 > 0) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 2, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log2(value); - return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); - } - } - - /** - * @dev Return the log in base 10 of a positive value rounded towards zero. - * Returns 0 if given 0. - */ - function log10(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >= 10 ** 64) { - value /= 10 ** 64; - result += 64; - } - if (value >= 10 ** 32) { - value /= 10 ** 32; - result += 32; - } - if (value >= 10 ** 16) { - value /= 10 ** 16; - result += 16; - } - if (value >= 10 ** 8) { - value /= 10 ** 8; - result += 8; - } - if (value >= 10 ** 4) { - value /= 10 ** 4; - result += 4; - } - if (value >= 10 ** 2) { - value /= 10 ** 2; - result += 2; - } - if (value >= 10 ** 1) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 10, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log10(value); - return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); - } - } - - /** - * @dev Return the log in base 256 of a positive value rounded towards zero. - * Returns 0 if given 0. - * - * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. - */ - function log256(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >> 128 > 0) { - value >>= 128; - result += 16; - } - if (value >> 64 > 0) { - value >>= 64; - result += 8; - } - if (value >> 32 > 0) { - value >>= 32; - result += 4; - } - if (value >> 16 > 0) { - value >>= 16; - result += 2; - } - if (value >> 8 > 0) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 256, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log256(value); - return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); - } - } - - /** - * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. - */ - function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { - return uint8(rounding) % 2 == 1; - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/IERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/IERC4626.sol deleted file mode 100644 index e1b778e..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/IERC4626.sol +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) - -pragma solidity ^0.8.20; - -import {IERC20} from "../token/ERC20/IERC20.sol"; -import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; - -/** - * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in - * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. - */ -interface IERC4626 is IERC20, IERC20Metadata { - 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 - ); - - /** - * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. - * - * - MUST be an ERC-20 token contract. - * - MUST NOT revert. - */ - function asset() external view returns (address assetTokenAddress); - - /** - * @dev Returns the total amount of the underlying asset that is “managed” by Vault. - * - * - SHOULD include any compounding that occurs from yield. - * - MUST be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT revert. - */ - function totalAssets() external view returns (uint256 totalManagedAssets); - - /** - * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal - * scenario where all the conditions are met. - * - * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT show any variations depending on the caller. - * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - * - MUST NOT revert. - * - * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the - * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and - * from. - */ - function convertToShares(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal - * scenario where all the conditions are met. - * - * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT show any variations depending on the caller. - * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - * - MUST NOT revert. - * - * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the - * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and - * from. - */ - function convertToAssets(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, - * through a deposit call. - * - * - MUST return a limited value if receiver is subject to some deposit limit. - * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. - * - MUST NOT revert. - */ - function maxDeposit(address receiver) external view returns (uint256 maxAssets); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given - * current on-chain conditions. - * - * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit - * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called - * in the same transaction. - * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the - * deposit would be accepted, regardless if the user has enough tokens approved, etc. - * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by depositing. - */ - function previewDeposit(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. - * - * - MUST emit the Deposit event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * deposit execution, and are accounted for during deposit. - * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not - * approving enough underlying tokens to the Vault contract, etc). - * - * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. - */ - function deposit(uint256 assets, address receiver) external returns (uint256 shares); - - /** - * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. - * - MUST return a limited value if receiver is subject to some mint limit. - * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. - * - MUST NOT revert. - */ - function maxMint(address receiver) external view returns (uint256 maxShares); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given - * current on-chain conditions. - * - * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call - * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the - * same transaction. - * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint - * would be accepted, regardless if the user has enough tokens approved, etc. - * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by minting. - */ - function previewMint(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. - * - * - MUST emit the Deposit event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint - * execution, and are accounted for during mint. - * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not - * approving enough underlying tokens to the Vault contract, etc). - * - * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. - */ - function mint(uint256 shares, address receiver) external returns (uint256 assets); - - /** - * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the - * Vault, through a withdraw call. - * - * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. - * - MUST NOT revert. - */ - function maxWithdraw(address owner) external view returns (uint256 maxAssets); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, - * given current on-chain conditions. - * - * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw - * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if - * called - * in the same transaction. - * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though - * the withdrawal would be accepted, regardless if the user has enough shares, etc. - * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by depositing. - */ - function previewWithdraw(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. - * - * - MUST emit the Withdraw event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * withdraw execution, and are accounted for during withdraw. - * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner - * not having enough shares, etc). - * - * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. - * Those methods should be performed separately. - */ - function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); - - /** - * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, - * through a redeem call. - * - * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. - * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. - * - MUST NOT revert. - */ - function maxRedeem(address owner) external view returns (uint256 maxShares); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, - * given current on-chain conditions. - * - * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call - * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the - * same transaction. - * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the - * redemption would be accepted, regardless if the user has enough shares, etc. - * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by redeeming. - */ - function previewRedeem(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. - * - * - MUST emit the Withdraw event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * redeem execution, and are accounted for during redeem. - * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner - * not having enough shares, etc). - * - * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. - * Those methods should be performed separately. - */ - function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/draft-IERC6093.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/draft-IERC6093.sol deleted file mode 100644 index c38379a..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/interfaces/draft-IERC6093.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -/** - * @dev Standard ERC20 Errors - * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. - */ -interface IERC20Errors { - /** - * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - * @param balance Current balance for the interacting account. - * @param needed Minimum amount required to perform a transfer. - */ - error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); - - /** - * @dev Indicates a failure with the token `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - */ - error ERC20InvalidSender(address sender); - - /** - * @dev Indicates a failure with the token `receiver`. Used in transfers. - * @param receiver Address to which tokens are being transferred. - */ - error ERC20InvalidReceiver(address receiver); - - /** - * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. - * @param spender Address that may be allowed to operate on tokens without being their owner. - * @param allowance Amount of tokens a `spender` is allowed to operate with. - * @param needed Minimum amount required to perform a transfer. - */ - error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); - - /** - * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. - * @param approver Address initiating an approval operation. - */ - error ERC20InvalidApprover(address approver); - - /** - * @dev Indicates a failure with the `spender` to be approved. Used in approvals. - * @param spender Address that may be allowed to operate on tokens without being their owner. - */ - error ERC20InvalidSpender(address spender); -} - -/** - * @dev Standard ERC721 Errors - * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. - */ -interface IERC721Errors { - /** - * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. - * Used in balance queries. - * @param owner Address of the current owner of a token. - */ - error ERC721InvalidOwner(address owner); - - /** - * @dev Indicates a `tokenId` whose `owner` is the zero address. - * @param tokenId Identifier number of a token. - */ - error ERC721NonexistentToken(uint256 tokenId); - - /** - * @dev Indicates an error related to the ownership over a particular token. Used in transfers. - * @param sender Address whose tokens are being transferred. - * @param tokenId Identifier number of a token. - * @param owner Address of the current owner of a token. - */ - error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); - - /** - * @dev Indicates a failure with the token `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - */ - error ERC721InvalidSender(address sender); - - /** - * @dev Indicates a failure with the token `receiver`. Used in transfers. - * @param receiver Address to which tokens are being transferred. - */ - error ERC721InvalidReceiver(address receiver); - - /** - * @dev Indicates a failure with the `operator`’s approval. Used in transfers. - * @param operator Address that may be allowed to operate on tokens without being their owner. - * @param tokenId Identifier number of a token. - */ - error ERC721InsufficientApproval(address operator, uint256 tokenId); - - /** - * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. - * @param approver Address initiating an approval operation. - */ - error ERC721InvalidApprover(address approver); - - /** - * @dev Indicates a failure with the `operator` to be approved. Used in approvals. - * @param operator Address that may be allowed to operate on tokens without being their owner. - */ - error ERC721InvalidOperator(address operator); -} - -/** - * @dev Standard ERC1155 Errors - * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. - */ -interface IERC1155Errors { - /** - * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - * @param balance Current balance for the interacting account. - * @param needed Minimum amount required to perform a transfer. - * @param tokenId Identifier number of a token. - */ - error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); - - /** - * @dev Indicates a failure with the token `sender`. Used in transfers. - * @param sender Address whose tokens are being transferred. - */ - error ERC1155InvalidSender(address sender); - - /** - * @dev Indicates a failure with the token `receiver`. Used in transfers. - * @param receiver Address to which tokens are being transferred. - */ - error ERC1155InvalidReceiver(address receiver); - - /** - * @dev Indicates a failure with the `operator`’s approval. Used in transfers. - * @param operator Address that may be allowed to operate on tokens without being their owner. - * @param owner Address of the current owner of a token. - */ - error ERC1155MissingApprovalForAll(address operator, address owner); - - /** - * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. - * @param approver Address initiating an approval operation. - */ - error ERC1155InvalidApprover(address approver); - - /** - * @dev Indicates a failure with the `operator` to be approved. Used in approvals. - * @param operator Address that may be allowed to operate on tokens without being their owner. - */ - error ERC1155InvalidOperator(address operator); - - /** - * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. - * Used in batch transfers. - * @param idsLength Length of the array of token identifiers - * @param valuesLength Length of the array of token amounts - */ - error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/DummyERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/DummyERC20.sol deleted file mode 100644 index bc1376a..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/DummyERC20.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "../../token/ERC20/ERC20.sol"; - -contract DummyERC20 is ERC20 { - constructor() ERC20("ERC20Mock2", "E20M2") {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC20Mock.sol deleted file mode 100644 index 39ab129..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC20Mock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "../../token/ERC20/ERC20.sol"; - -contract ERC20Mock is ERC20 { - constructor() ERC20("ERC20Mock", "E20M") {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC4626Mock.sol deleted file mode 100644 index 5ce832e..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/mocks/token/ERC4626Mock.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; -import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; - -contract ERC4626Mock is ERC4626 { - constructor(address underlying) ERC4626(IERC20(underlying)) {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/ERC20.sol deleted file mode 100644 index 8eeb314..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/ERC20.sol +++ /dev/null @@ -1,372 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) - -pragma solidity ^0.8.20; - -import {IERC20} from "./IERC20.sol"; -import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; -import {Context} from "../../utils/Context.sol"; -import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; - -/** - * @dev Implementation of the {IERC20} interface. - * - * This implementation is agnostic to the way tokens are created. This means - * that a supply mechanism has to be added in a derived contract using {_mint}. - * - * TIP: For a detailed writeup see our guide - * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * The default value of {decimals} is 18. To change this, you should override - * this function so it returns a different value. - * - * We have followed general OpenZeppelin Contracts guidelines: functions revert - * instead returning `false` on failure. This behavior is nonetheless - * conventional and does not conflict with the expectations of ERC20 - * applications. - * - * Additionally, an {Approval} event is emitted on calls to {transferFrom}. - * This allows applications to reconstruct the allowance for all accounts just - * by listening to said events. Other implementations of the EIP may not emit - * these events, as it isn't required by the specification. - * - * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} - * functions have been added to mitigate the well-known issues around setting - * allowances. See {IERC20-approve}. - */ -abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { - mapping(address account => uint256) private _balances; - - mapping(address account => mapping(address spender => uint256)) private _allowances; - - uint256 private _totalSupply; - - string private _name; - string private _symbol; - - /** - * @dev Indicates a failed `decreaseAllowance` request. - */ - error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); - - /** - * @dev Sets the values for {name} and {symbol}. - * - * All two of these values are immutable: they can only be set once during - * construction. - */ - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5.05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the default value returned by this function, unless - * it's overridden. - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view virtual returns (uint8) { - return 18; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view virtual returns (uint256) { - return _totalSupply; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual returns (uint256) { - return _balances[account]; - } - - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `value`. - */ - function transfer(address to, uint256 value) public virtual returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, value); - return true; - } - - /** - * @dev See {IERC20-allowance}. - */ - function allowance(address owner, address spender) public view virtual returns (uint256) { - return _allowances[owner][spender]; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 value) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, value); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `value`. - * - the caller must have allowance for ``from``'s tokens of at least - * `value`. - */ - function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, value); - _transfer(from, to, value); - return true; - } - - /** - * @dev Atomically increases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, allowance(owner, spender) + addedValue); - return true; - } - - /** - * @dev Atomically decreases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `spender` must have allowance for the caller of at least - * `requestedDecrease`. - * - * NOTE: Although this function is designed to avoid double spending with {approval}, - * it can still be frontrunned, preventing any attempt of allowance reduction. - */ - function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { - address owner = _msgSender(); - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance < requestedDecrease) { - revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); - } - unchecked { - _approve(owner, spender, currentAllowance - requestedDecrease); - } - - return true; - } - - /** - * @dev Moves a `value` amount of tokens from `from` to `to`. - * - * This internal function is equivalent to {transfer}, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a {Transfer} event. - * - * NOTE: This function is not virtual, {_update} should be overridden instead. - */ - function _transfer(address from, address to, uint256 value) internal { - if (from == address(0)) { - revert ERC20InvalidSender(address(0)); - } - if (to == address(0)) { - revert ERC20InvalidReceiver(address(0)); - } - _update(from, to, value); - } - - /** - * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is - * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. - * - * Emits a {Transfer} event. - */ - function _update(address from, address to, uint256 value) internal virtual { - if (from == address(0)) { - // Overflow check required: The rest of the code assumes that totalSupply never overflows - _totalSupply += value; - } else { - uint256 fromBalance = _balances[from]; - if (fromBalance < value) { - revert ERC20InsufficientBalance(from, fromBalance, value); - } - unchecked { - // Overflow not possible: value <= fromBalance <= totalSupply. - _balances[from] = fromBalance - value; - } - } - - if (to == address(0)) { - unchecked { - // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. - _totalSupply -= value; - } - } else { - unchecked { - // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. - _balances[to] += value; - } - } - - emit Transfer(from, to, value); - } - - /** - * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). - * Relies on the `_update` mechanism - * - * Emits a {Transfer} event with `from` set to the zero address. - * - * NOTE: This function is not virtual, {_update} should be overridden instead. - */ - function _mint(address account, uint256 value) internal { - if (account == address(0)) { - revert ERC20InvalidReceiver(address(0)); - } - _update(address(0), account, value); - } - - /** - * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. - * Relies on the `_update` mechanism. - * - * Emits a {Transfer} event with `to` set to the zero address. - * - * NOTE: This function is not virtual, {_update} should be overridden instead - */ - function _burn(address account, uint256 value) internal { - if (account == address(0)) { - revert ERC20InvalidSender(address(0)); - } - _update(account, address(0), value); - } - - /** - * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. - * - * This internal function is equivalent to `approve`, and can be used to - * e.g. set automatic allowances for certain subsystems, etc. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `owner` cannot be the zero address. - * - `spender` cannot be the zero address. - * - * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. - */ - function _approve(address owner, address spender, uint256 value) internal { - _approve(owner, spender, value, true); - } - - /** - * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. - * - * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by - * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any - * `Approval` event during `transferFrom` operations. - * - * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true - * using the following override: - * ``` - * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { - * super._approve(owner, spender, value, true); - * } - * ``` - * - * Requirements are the same as {_approve}. - */ - function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { - if (owner == address(0)) { - revert ERC20InvalidApprover(address(0)); - } - if (spender == address(0)) { - revert ERC20InvalidSpender(address(0)); - } - _allowances[owner][spender] = value; - if (emitEvent) { - emit Approval(owner, spender, value); - } - } - - /** - * @dev Updates `owner` s allowance for `spender` based on spent `value`. - * - * Does not update the allowance value in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance(address owner, address spender, uint256 value) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - if (currentAllowance < value) { - revert ERC20InsufficientAllowance(spender, currentAllowance, value); - } - unchecked { - _approve(owner, spender, currentAllowance - value, false); - } - } - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/IERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/IERC20.sol deleted file mode 100644 index 77ca716..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/IERC20.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Interface of the ERC20 standard as defined in the EIP. - */ -interface IERC20 { - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); - - /** - * @dev Returns the value of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the value of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves a `value` amount of tokens from the caller's account to `to`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address to, uint256 value) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets a `value` amount of tokens as the allowance of `spender` over the - * caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 value) external returns (bool); - - /** - * @dev Moves a `value` amount of tokens from `from` to `to` using the - * allowance mechanism. `value` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom(address from, address to, uint256 value) external returns (bool); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/ERC4626.sol deleted file mode 100644 index a8296fb..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/ERC4626.sol +++ /dev/null @@ -1,319 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) - -pragma solidity ^0.8.20; - -import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; -import {SafeERC20} from "../utils/SafeERC20.sol"; -import {IERC4626} from "../../../interfaces/IERC4626.sol"; -import {Math} from "../../../utils/math/Math.sol"; - -/** - * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in - * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. - * - * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for - * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends - * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this - * contract and not the "assets" token which is an independent contract. - * - * [CAUTION] - * ==== - * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning - * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation - * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial - * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may - * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by - * verifying the amount received is as expected, using a wrapper that performs these checks such as - * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. - * - * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` - * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault - * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself - * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset - * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's - * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more - * expensive than it is profitable. More details about the underlying math can be found - * xref:erc4626.adoc#inflation-attack[here]. - * - * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued - * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets - * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience - * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the - * `_convertToShares` and `_convertToAssets` functions. - * - * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. - * ==== - */ -contract ERC4626 is ERC20, IERC4626 { - using Math for uint256; - - IERC20 private immutable _asset; - uint8 private immutable _decimals; - - /** - * @dev Attempted to deposit more assets than the max amount for `receiver`. - */ - error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); - - /** - * @dev Attempted to mint more shares than the max amount for `receiver`. - */ - error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); - - /** - * @dev Attempted to withdraw more assets than the max amount for `receiver`. - */ - error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); - - /** - * @dev Attempted to redeem more shares than the max amount for `receiver`. - */ - error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - - /** - * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). - */ - constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { - (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); - _underlyingDecimals = success ? assetDecimals : 18; - _asset = asset_; - } - - /** - * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. - */ - function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { - (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( - abi.encodeCall(IERC20Metadata.decimals, ()) - ); - if (success && encodedDecimals.length >= 32) { - uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); - if (returnedDecimals <= type(uint8).max) { - return (true, uint8(returnedDecimals)); - } - } - return (false, 0); - } - - /** - * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This - * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the - * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. - * - * See {IERC20Metadata-decimals}. - */ - function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { - return _decimals; - } - - /** @dev See {IERC4626-asset}. */ - function asset() public view virtual returns (address) { - return address(_asset); - } - - /** @dev See {IERC4626-totalAssets}. */ - function totalAssets() public view virtual returns (uint256) { - return _asset.balanceOf(address(this)); - } - - /** @dev See {IERC4626-convertToShares}. */ - function convertToShares(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); - } - - /** @dev See {IERC4626-convertToAssets}. */ - function convertToAssets(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Floor); - } - - /** @dev See {IERC4626-maxDeposit}. */ - function maxDeposit(address) public view virtual returns (uint256) { - return _isVaultHealthy() ? type(uint256).max : 0; - } - - /** @dev See {IERC4626-maxMint}. */ - function maxMint(address) public view virtual returns (uint256) { - return type(uint256).max; - } - - /** @dev See {IERC4626-maxWithdraw}. */ - function maxWithdraw(address owner) public view virtual returns (uint256) { - return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); - } - - /** @dev See {IERC4626-maxRedeem}. */ - function maxRedeem(address owner) public view virtual returns (uint256) { - return balanceOf(owner); - } - - /** @dev See {IERC4626-previewDeposit}. */ - function previewDeposit(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); - } - - /** @dev See {IERC4626-previewMint}. */ - function previewMint(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Ceil); - } - - /** @dev See {IERC4626-previewWithdraw}. */ - function previewWithdraw(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Ceil); - } - - /** @dev See {IERC4626-previewRedeem}. */ - function previewRedeem(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Floor); - } - - /** @dev See {IERC4626-deposit}. */ - function deposit(uint256 assets, address receiver) public virtual returns (uint256) { - uint256 maxAssets = maxDeposit(receiver); - if (assets > maxAssets) { - revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); - } - - uint256 shares = previewDeposit(assets); - _deposit(_msgSender(), receiver, assets, shares); - - return shares; - } - - /** @dev See {IERC4626-mint}. - * - * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. - * In this case, the shares will be minted without requiring any assets to be deposited. - */ - function mint(uint256 shares, address receiver) public virtual returns (uint256) { - uint256 maxShares = maxMint(receiver); - if (shares > maxShares) { - revert ERC4626ExceededMaxMint(receiver, shares, maxShares); - } - - uint256 assets = previewMint(shares); - _deposit(_msgSender(), receiver, assets, shares); - - return assets; - } - - /** @dev See {IERC4626-withdraw}. */ - function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { - uint256 maxAssets = maxWithdraw(owner); - if (assets > maxAssets) { - revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); - } - - uint256 shares = previewWithdraw(assets); - _withdraw(_msgSender(), receiver, owner, assets, shares); - - return shares; - } - - /** @dev See {IERC4626-redeem}. */ - function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { - uint256 maxShares = maxRedeem(owner); - if (shares > maxShares) { - revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); - } - - uint256 assets = previewRedeem(shares); - _withdraw(_msgSender(), receiver, owner, assets, shares); - - return assets; - } - - /** - * @dev Internal conversion function (from assets to shares) with support for rounding direction. - */ - function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); - return - (assets == 0 || supply == 0) - ? _initialConvertToShares(assets, rounding) - : assets.mulDiv(supply, totalAssets(), rounding); - } - - /** - * @dev Internal conversion function (from assets to shares) to apply when the vault is empty. - * - * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it. - */ - function _initialConvertToShares( - uint256 assets, - Math.Rounding /*rounding*/ - ) internal view virtual returns (uint256 shares) { - return assets; - } - - /** - * @dev Internal conversion function (from shares to assets) with support for rounding direction. - */ - function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); - return - (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding); - } - - /** - * @dev Internal conversion function (from shares to assets) to apply when the vault is empty. - * - * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it. - */ - function _initialConvertToAssets( - uint256 shares, - Math.Rounding /*rounding*/ - ) internal view virtual returns (uint256) { - return shares; - } - - /** - * @dev Deposit/mint common workflow. - */ - function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { - // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the - // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, - // calls the vault, which is assumed not malicious. - // - // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the - // assets are transferred and before the shares are minted, which is a valid state. - // slither-disable-next-line reentrancy-no-eth - SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); - _mint(receiver, shares); - - emit Deposit(caller, receiver, assets, shares); - } - - /** - * @dev Withdraw/redeem common workflow. - */ - function _withdraw( - address caller, - address receiver, - address owner, - uint256 assets, - uint256 shares - ) internal virtual { - if (caller != owner) { - _spendAllowance(owner, caller, shares); - } - - // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the - // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, - // calls the vault, which is assumed not malicious. - // - // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the - // shares are burned and after the assets are transferred, which is a valid state. - _burn(owner, shares); - SafeERC20.safeTransfer(_asset, receiver, assets); - - emit Withdraw(caller, receiver, owner, assets, shares); - } - - /** - * @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares. - */ - function _isVaultHealthy() private view returns (bool) { - return totalAssets() > 0 || totalSupply() == 0; - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol deleted file mode 100644 index 9056e34..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) - -pragma solidity ^0.8.20; - -import {IERC20} from "../IERC20.sol"; - -/** - * @dev Interface for the optional metadata functions from the ERC20 standard. - */ -interface IERC20Metadata is IERC20 { - /** - * @dev Returns the name of the token. - */ - function name() external view returns (string memory); - - /** - * @dev Returns the symbol of the token. - */ - function symbol() external view returns (string memory); - - /** - * @dev Returns the decimals places of the token. - */ - function decimals() external view returns (uint8); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol deleted file mode 100644 index 2370410..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in - * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - * - * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by - * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't - * need to send a transaction, and thus is not required to hold Ether at all. - */ -interface IERC20Permit { - /** - * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, - * given ``owner``'s signed approval. - * - * IMPORTANT: The same issues {IERC20-approve} has related to transaction - * ordering also apply here. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `deadline` must be a timestamp in the future. - * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` - * over the EIP712-formatted function arguments. - * - the signature must use ``owner``'s current nonce (see {nonces}). - * - * For more information on the signature format, see the - * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP - * section]. - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @dev Returns the current nonce for `owner`. This value must be - * included whenever a signature is generated for {permit}. - * - * Every successful call to {permit} increases ``owner``'s nonce by one. This - * prevents a signature from being used multiple times. - */ - function nonces(address owner) external view returns (uint256); - - /** - * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32); -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/utils/SafeERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/utils/SafeERC20.sol deleted file mode 100644 index fcdbbae..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/token/ERC20/utils/SafeERC20.sol +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) - -pragma solidity ^0.8.20; - -import {IERC20} from "../IERC20.sol"; -import {IERC20Permit} from "../extensions/IERC20Permit.sol"; -import {Address} from "../../../utils/Address.sol"; - -/** - * @title SafeERC20 - * @dev Wrappers around ERC20 operations that throw on failure (when the token - * contract returns false). Tokens that return no value (and instead revert or - * throw on failure) are also supported, non-reverting calls are assumed to be - * successful. - * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, - * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. - */ -library SafeERC20 { - using Address for address; - - /** - * @dev An operation with an ERC20 token failed. - */ - error SafeERC20FailedOperation(address token); - - /** - * @dev Indicates a failed `decreaseAllowance` request. - */ - error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); - - /** - * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeTransfer(IERC20 token, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); - } - - /** - * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the - * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. - */ - function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); - } - - /** - * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 oldAllowance = token.allowance(address(this), spender); - forceApprove(token, spender, oldAllowance + value); - } - - /** - * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, - * non-reverting calls are assumed to be successful. - */ - function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { - unchecked { - uint256 currentAllowance = token.allowance(address(this), spender); - if (currentAllowance < requestedDecrease) { - revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); - } - forceApprove(token, spender, currentAllowance - requestedDecrease); - } - } - - /** - * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, - * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval - * to be set to zero before setting it to a non-zero value, such as USDT. - */ - function forceApprove(IERC20 token, address spender, uint256 value) internal { - bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); - - if (!_callOptionalReturnBool(token, approvalCall)) { - _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); - _callOptionalReturn(token, approvalCall); - } - } - - /** - * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. - * Revert on invalid signature. - */ - function safePermit( - IERC20Permit token, - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) internal { - uint256 nonceBefore = token.nonces(owner); - token.permit(owner, spender, value, deadline, v, r, s); - uint256 nonceAfter = token.nonces(owner); - if (nonceAfter != nonceBefore + 1) { - revert SafeERC20FailedOperation(address(token)); - } - } - - /** - * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - * on the return value: the return value is optional (but if data is returned, it must not be false). - * @param token The token targeted by the call. - * @param data The call data (encoded using abi.encode or one of its variants). - */ - function _callOptionalReturn(IERC20 token, bytes memory data) private { - // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since - // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that - // the target address contains contract code and also asserts for success in the low-level call. - - bytes memory returndata = address(token).functionCall(data); - if (returndata.length != 0 && !abi.decode(returndata, (bool))) { - revert SafeERC20FailedOperation(address(token)); - } - } - - /** - * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - * on the return value: the return value is optional (but if data is returned, it must not be false). - * @param token The token targeted by the call. - * @param data The call data (encoded using abi.encode or one of its variants). - * - * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. - */ - function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { - // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since - // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false - // and not revert is the subcall reverts. - - (bool success, bytes memory returndata) = address(token).call(data); - return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Address.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Address.sol deleted file mode 100644 index fd22b05..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Address.sol +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev The ETH balance of the account is not enough to perform the operation. - */ - error AddressInsufficientBalance(address account); - - /** - * @dev There's no code at `target` (it is not a contract). - */ - error AddressEmptyCode(address target); - - /** - * @dev A call to an address target failed. The target may have reverted. - */ - error FailedInnerCall(); - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - if (address(this).balance < amount) { - revert AddressInsufficientBalance(address(this)); - } - - (bool success, ) = recipient.call{value: amount}(""); - if (!success) { - revert FailedInnerCall(); - } - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason or custom error, it is bubbled - * up by this function (like regular Solidity function calls). However, if - * the call reverted with no returned reason, this function reverts with a - * {FailedInnerCall} error. - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - */ - function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { - if (address(this).balance < value) { - revert AddressInsufficientBalance(address(this)); - } - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResultFromTarget(target, success, returndata); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResultFromTarget(target, success, returndata); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResultFromTarget(target, success, returndata); - } - - /** - * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target - * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an - * unsuccessful call. - */ - function verifyCallResultFromTarget( - address target, - bool success, - bytes memory returndata - ) internal view returns (bytes memory) { - if (!success) { - _revert(returndata); - } else { - // only check if target is a contract if the call was successful and the return data is empty - // otherwise we already know that it was a contract - if (returndata.length == 0 && target.code.length == 0) { - revert AddressEmptyCode(target); - } - return returndata; - } - } - - /** - * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the - * revert reason or with a default {FailedInnerCall} error. - */ - function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { - if (!success) { - _revert(returndata); - } else { - return returndata; - } - } - - /** - * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. - */ - function _revert(bytes memory returndata) private pure { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert FailedInnerCall(); - } - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Context.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Context.sol deleted file mode 100644 index 25e1159..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/Context.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Provides information about the current execution context, including the - * sender of the transaction and its data. While these are generally available - * via msg.sender and msg.data, they should not be accessed in such a direct - * manner, since when dealing with meta-transactions the account sending and - * paying for execution may not be the actual sender (as far as an application - * is concerned). - * - * This contract is only required for intermediate, library-like contracts. - */ -abstract contract Context { - function _msgSender() internal view virtual returns (address) { - return msg.sender; - } - - function _msgData() internal view virtual returns (bytes calldata) { - return msg.data; - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/math/Math.sol deleted file mode 100644 index 17ce4c8..0000000 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts/utils/math/Math.sol +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) - -pragma solidity ^0.8.20; - -/** - * @dev Standard math utilities missing in the Solidity language. - */ -library Math { - /** - * @dev Muldiv operation overflow. - */ - error MathOverflowedMulDiv(); - - enum Rounding { - Floor, // Toward negative infinity - Ceil, // Toward positive infinity - Trunc, // Toward zero - Expand // Away from zero - } - - /** - * @dev Returns the addition of two unsigned integers, with an overflow flag. - */ - function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - uint256 c = a + b; - if (c < a) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the subtraction of two unsigned integers, with an overflow flag. - */ - function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b > a) return (false, 0); - return (true, a - b); - } - } - - /** - * @dev Returns the multiplication of two unsigned integers, with an overflow flag. - */ - function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 - if (a == 0) return (true, 0); - uint256 c = a * b; - if (c / a != b) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the division of two unsigned integers, with a division by zero flag. - */ - function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a / b); - } - } - - /** - * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. - */ - function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a % b); - } - } - - /** - * @dev Returns the largest of two numbers. - */ - function max(uint256 a, uint256 b) internal pure returns (uint256) { - return a > b ? a : b; - } - - /** - * @dev Returns the smallest of two numbers. - */ - function min(uint256 a, uint256 b) internal pure returns (uint256) { - return a < b ? a : b; - } - - /** - * @dev Returns the average of two numbers. The result is rounded towards - * zero. - */ - function average(uint256 a, uint256 b) internal pure returns (uint256) { - // (a + b) / 2 can overflow. - return (a & b) + (a ^ b) / 2; - } - - /** - * @dev Returns the ceiling of the division of two numbers. - * - * This differs from standard division with `/` in that it rounds towards infinity instead - * of rounding towards zero. - */ - function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { - if (b == 0) { - // Guarantee the same behavior as in a regular Solidity division. - return a / b; - } - - // (a + b - 1) / b can overflow on addition, so we distribute. - return a == 0 ? 0 : (a - 1) / b + 1; - } - - /** - * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 - * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) - * with further edits by Uniswap Labs also under MIT license. - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { - unchecked { - // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use - // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 - // variables such that product = prod1 * 2^256 + prod0. - uint256 prod0 = x * y; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(x, y, not(0)) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - // Handle non-overflow cases, 256 by 256 division. - if (prod1 == 0) { - // Solidity will revert if denominator == 0, unlike the div opcode on its own. - // The surrounding unchecked block does not change this fact. - // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. - return prod0 / denominator; - } - - // Make sure the result is less than 2^256. Also prevents denominator == 0. - if (denominator <= prod1) { - revert MathOverflowedMulDiv(); - } - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0]. - uint256 remainder; - assembly { - // Compute remainder using mulmod. - remainder := mulmod(x, y, denominator) - - // Subtract 256 bit number from 512 bit number. - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. - // See https://cs.stackexchange.com/q/138556/92363. - - uint256 twos = denominator & (0 - denominator); - assembly { - // Divide denominator by twos. - denominator := div(denominator, twos) - - // Divide [prod1 prod0] by twos. - prod0 := div(prod0, twos) - - // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. - twos := add(div(sub(0, twos), twos), 1) - } - - // Shift in bits from prod1 into prod0. - prod0 |= prod1 * twos; - - // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such - // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for - // four bits. That is, denominator * inv = 1 mod 2^4. - uint256 inverse = (3 * denominator) ^ 2; - - // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works - // in modular arithmetic, doubling the correct bits in each step. - inverse *= 2 - denominator * inverse; // inverse mod 2^8 - inverse *= 2 - denominator * inverse; // inverse mod 2^16 - inverse *= 2 - denominator * inverse; // inverse mod 2^32 - inverse *= 2 - denominator * inverse; // inverse mod 2^64 - inverse *= 2 - denominator * inverse; // inverse mod 2^128 - inverse *= 2 - denominator * inverse; // inverse mod 2^256 - - // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. - // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is - // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 - // is no longer required. - result = prod0 * inverse; - return result; - } - } - - /** - * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { - uint256 result = mulDiv(x, y, denominator); - if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { - result += 1; - } - return result; - } - - /** - * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded - * towards zero. - * - * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). - */ - function sqrt(uint256 a) internal pure returns (uint256) { - if (a == 0) { - return 0; - } - - // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. - // - // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have - // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. - // - // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` - // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` - // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` - // - // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. - uint256 result = 1 << (log2(a) >> 1); - - // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, - // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at - // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision - // into the expected uint128 result. - unchecked { - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - return min(result, a / result); - } - } - - /** - * @notice Calculates sqrt(a), following the selected rounding direction. - */ - function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = sqrt(a); - return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); - } - } - - /** - * @dev Return the log in base 2 of a positive value rounded towards zero. - * Returns 0 if given 0. - */ - function log2(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >> 128 > 0) { - value >>= 128; - result += 128; - } - if (value >> 64 > 0) { - value >>= 64; - result += 64; - } - if (value >> 32 > 0) { - value >>= 32; - result += 32; - } - if (value >> 16 > 0) { - value >>= 16; - result += 16; - } - if (value >> 8 > 0) { - value >>= 8; - result += 8; - } - if (value >> 4 > 0) { - value >>= 4; - result += 4; - } - if (value >> 2 > 0) { - value >>= 2; - result += 2; - } - if (value >> 1 > 0) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 2, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log2(value); - return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); - } - } - - /** - * @dev Return the log in base 10 of a positive value rounded towards zero. - * Returns 0 if given 0. - */ - function log10(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >= 10 ** 64) { - value /= 10 ** 64; - result += 64; - } - if (value >= 10 ** 32) { - value /= 10 ** 32; - result += 32; - } - if (value >= 10 ** 16) { - value /= 10 ** 16; - result += 16; - } - if (value >= 10 ** 8) { - value /= 10 ** 8; - result += 8; - } - if (value >= 10 ** 4) { - value /= 10 ** 4; - result += 4; - } - if (value >= 10 ** 2) { - value /= 10 ** 2; - result += 2; - } - if (value >= 10 ** 1) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 10, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log10(value); - return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); - } - } - - /** - * @dev Return the log in base 256 of a positive value rounded towards zero. - * Returns 0 if given 0. - * - * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. - */ - function log256(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >> 128 > 0) { - value >>= 128; - result += 16; - } - if (value >> 64 > 0) { - value >>= 64; - result += 8; - } - if (value >> 32 > 0) { - value >>= 32; - result += 4; - } - if (value >> 16 > 0) { - value >>= 16; - result += 2; - } - if (value >> 8 > 0) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 256, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log256(value); - return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); - } - } - - /** - * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. - */ - function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { - return uint8(rounding) % 2 == 1; - } -} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/IERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/interfaces/IERC4626.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/IERC4626.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/interfaces/IERC4626.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/draft-IERC6093.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/interfaces/draft-IERC6093.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/interfaces/draft-IERC6093.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/interfaces/draft-IERC6093.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC20Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC20Mock.sol similarity index 80% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC20Mock.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC20Mock.sol index 39ab129..cc75923 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/mocks/token/ERC20Mock.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC20Mock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; -import {ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC20} from "../token/ERC20/ERC20.sol"; contract ERC20Mock is ERC20 { constructor() ERC20("ERC20Mock", "E20M") {} diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC4626Mock.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC4626Mock.sol similarity index 55% rename from lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC4626Mock.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC4626Mock.sol index 5ce832e..ef2d1a4 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/mocks/token/ERC4626Mock.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC4626Mock.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; -import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; -import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; +import "../token/ERC20/extensions/ERC4626.sol"; contract ERC4626Mock is ERC4626 { - constructor(address underlying) ERC4626(IERC20(underlying)) {} + constructor(address underlying) ERC20("ERC4626Mock", "E4626M") ERC4626(IERC20(underlying)) {} function mint(address account, uint256 amount) external { _mint(account, amount); diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/ERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/ERC20.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/ERC20.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/ERC20.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/IERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/IERC20.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/IERC20.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/IERC20.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/ERC4626.sol similarity index 61% rename from lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/ERC4626.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/ERC4626.sol index a8296fb..663377d 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7a-fixed/contracts copy/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/ERC4626.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) +// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC4626.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; -import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; -import {SafeERC20} from "../utils/SafeERC20.sol"; -import {IERC4626} from "../../../interfaces/IERC4626.sol"; -import {Math} from "../../../utils/math/Math.sol"; +import "../ERC20.sol"; +import "../utils/SafeERC20.sol"; +import "../../../interfaces/IERC4626.sol"; +import "../../../utils/math/Math.sol"; /** * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in @@ -17,66 +17,28 @@ import {Math} from "../../../utils/math/Math.sol"; * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this * contract and not the "assets" token which is an independent contract. * - * [CAUTION] - * ==== - * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning - * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * CAUTION: When the vault is empty or nearly empty, deposits are at high risk of being stolen through frontrunning with + * a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may - * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * similarly be affected by slippage. Users can protect against this attack as well unexpected slippage in general by * verifying the amount received is as expected, using a wrapper that performs these checks such as * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. * - * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` - * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault - * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself - * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset - * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's - * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more - * expensive than it is profitable. More details about the underlying math can be found - * xref:erc4626.adoc#inflation-attack[here]. - * - * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued - * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets - * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience - * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the - * `_convertToShares` and `_convertToAssets` functions. - * - * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. - * ==== + * _Available since v4.7._ */ -contract ERC4626 is ERC20, IERC4626 { +abstract contract ERC4626 is ERC20, IERC4626 { using Math for uint256; IERC20 private immutable _asset; uint8 private immutable _decimals; - /** - * @dev Attempted to deposit more assets than the max amount for `receiver`. - */ - error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); - - /** - * @dev Attempted to mint more shares than the max amount for `receiver`. - */ - error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); - - /** - * @dev Attempted to withdraw more assets than the max amount for `receiver`. - */ - error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); - - /** - * @dev Attempted to redeem more shares than the max amount for `receiver`. - */ - error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); - /** * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ - constructor(IERC20 asset_)ERC20("ERC20Mock", "E20M") { + constructor(IERC20 asset_) { (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); - _underlyingDecimals = success ? assetDecimals : 18; + _decimals = success ? assetDecimals : super.decimals(); _asset = asset_; } @@ -85,7 +47,7 @@ contract ERC4626 is ERC20, IERC4626 { */ function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( - abi.encodeCall(IERC20Metadata.decimals, ()) + abi.encodeWithSelector(IERC20Metadata.decimals.selector) ); if (success && encodedDecimals.length >= 32) { uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); @@ -97,10 +59,9 @@ contract ERC4626 is ERC20, IERC4626 { } /** - * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This - * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the - * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. - * + * @dev Decimals are read from the underlying asset in the constructor and cached. If this fails (e.g., the asset + * has not been created yet), the cached value is set to a default obtained by `super.decimals()` (which depends on + * inheritance but is most likely 18). Override this function in order to set a guaranteed hardcoded value. * See {IERC20Metadata-decimals}. */ function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { @@ -108,71 +69,68 @@ contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-asset}. */ - function asset() public view virtual returns (address) { + function asset() public view virtual override returns (address) { return address(_asset); } /** @dev See {IERC4626-totalAssets}. */ - function totalAssets() public view virtual returns (uint256) { + function totalAssets() public view virtual override returns (uint256) { return _asset.balanceOf(address(this)); } /** @dev See {IERC4626-convertToShares}. */ - function convertToShares(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); + function convertToShares(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Down); } /** @dev See {IERC4626-convertToAssets}. */ - function convertToAssets(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Floor); + function convertToAssets(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Down); } /** @dev See {IERC4626-maxDeposit}. */ - function maxDeposit(address) public view virtual returns (uint256) { + function maxDeposit(address) public view virtual override returns (uint256) { return _isVaultHealthy() ? type(uint256).max : 0; } /** @dev See {IERC4626-maxMint}. */ - function maxMint(address) public view virtual returns (uint256) { + function maxMint(address) public view virtual override returns (uint256) { return type(uint256).max; } /** @dev See {IERC4626-maxWithdraw}. */ - function maxWithdraw(address owner) public view virtual returns (uint256) { - return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + function maxWithdraw(address owner) public view virtual override returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Down); } /** @dev See {IERC4626-maxRedeem}. */ - function maxRedeem(address owner) public view virtual returns (uint256) { + function maxRedeem(address owner) public view virtual override returns (uint256) { return balanceOf(owner); } /** @dev See {IERC4626-previewDeposit}. */ - function previewDeposit(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Floor); + function previewDeposit(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Down); } /** @dev See {IERC4626-previewMint}. */ - function previewMint(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Ceil); + function previewMint(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Up); } /** @dev See {IERC4626-previewWithdraw}. */ - function previewWithdraw(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Ceil); + function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { + return _convertToShares(assets, Math.Rounding.Up); } /** @dev See {IERC4626-previewRedeem}. */ - function previewRedeem(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Floor); + function previewRedeem(uint256 shares) public view virtual override returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Down); } /** @dev See {IERC4626-deposit}. */ - function deposit(uint256 assets, address receiver) public virtual returns (uint256) { - uint256 maxAssets = maxDeposit(receiver); - if (assets > maxAssets) { - revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); - } + function deposit(uint256 assets, address receiver) public virtual override returns (uint256) { + require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max"); uint256 shares = previewDeposit(assets); _deposit(_msgSender(), receiver, assets, shares); @@ -185,11 +143,8 @@ contract ERC4626 is ERC20, IERC4626 { * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. * In this case, the shares will be minted without requiring any assets to be deposited. */ - function mint(uint256 shares, address receiver) public virtual returns (uint256) { - uint256 maxShares = maxMint(receiver); - if (shares > maxShares) { - revert ERC4626ExceededMaxMint(receiver, shares, maxShares); - } + function mint(uint256 shares, address receiver) public virtual override returns (uint256) { + require(shares <= maxMint(receiver), "ERC4626: mint more than max"); uint256 assets = previewMint(shares); _deposit(_msgSender(), receiver, assets, shares); @@ -198,11 +153,8 @@ contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-withdraw}. */ - function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { - uint256 maxAssets = maxWithdraw(owner); - if (assets > maxAssets) { - revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); - } + function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { + require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); uint256 shares = previewWithdraw(assets); _withdraw(_msgSender(), receiver, owner, assets, shares); @@ -211,11 +163,8 @@ contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-redeem}. */ - function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { - uint256 maxShares = maxRedeem(owner); - if (shares > maxShares) { - revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); - } + function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { + require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); uint256 assets = previewRedeem(shares); _withdraw(_msgSender(), receiver, owner, assets, shares); @@ -225,9 +174,12 @@ contract ERC4626 is ERC20, IERC4626 { /** * @dev Internal conversion function (from assets to shares) with support for rounding direction. + * + * Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset + * would represent an infinite amount of shares. */ function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); + uint256 supply = totalSupply(); return (assets == 0 || supply == 0) ? _initialConvertToShares(assets, rounding) @@ -271,7 +223,7 @@ contract ERC4626 is ERC20, IERC4626 { * @dev Deposit/mint common workflow. */ function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { - // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, // calls the vault, which is assumed not malicious. // diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Metadata.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Metadata.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Permit.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/extensions/IERC20Permit.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/utils/SafeERC20.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/utils/SafeERC20.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/token/ERC20/utils/SafeERC20.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/utils/SafeERC20.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Address.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/Address.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Address.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/Address.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Context.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/Context.sol similarity index 100% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/Context.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/Context.sol diff --git a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/math/Math.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/math/Math.sol similarity index 74% rename from lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/math/Math.sol rename to lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/math/Math.sol index 17ce4c8..8400d06 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts copy/utils/math/Math.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/math/Math.sol @@ -1,78 +1,16 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) +// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.0; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { - /** - * @dev Muldiv operation overflow. - */ - error MathOverflowedMulDiv(); - enum Rounding { - Floor, // Toward negative infinity - Ceil, // Toward positive infinity - Trunc, // Toward zero - Expand // Away from zero - } - - /** - * @dev Returns the addition of two unsigned integers, with an overflow flag. - */ - function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - uint256 c = a + b; - if (c < a) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the subtraction of two unsigned integers, with an overflow flag. - */ - function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b > a) return (false, 0); - return (true, a - b); - } - } - - /** - * @dev Returns the multiplication of two unsigned integers, with an overflow flag. - */ - function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 - if (a == 0) return (true, 0); - uint256 c = a * b; - if (c / a != b) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the division of two unsigned integers, with a division by zero flag. - */ - function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a / b); - } - } - - /** - * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. - */ - function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a % b); - } + Down, // Toward negative infinity + Up, // Toward infinity + Zero // Toward zero } /** @@ -101,15 +39,10 @@ library Math { /** * @dev Returns the ceiling of the division of two numbers. * - * This differs from standard division with `/` in that it rounds towards infinity instead - * of rounding towards zero. + * This differs from standard division with `/` in that it rounds up instead + * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { - if (b == 0) { - // Guarantee the same behavior as in a regular Solidity division. - return a / b; - } - // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } @@ -124,25 +57,21 @@ library Math { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. - uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) + prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { - // Solidity will revert if denominator == 0, unlike the div opcode on its own. - // The surrounding unchecked block does not change this fact. - // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. - if (denominator <= prod1) { - revert MathOverflowedMulDiv(); - } + require(denominator > prod1, "Math: mulDiv overflow"); /////////////////////////////////////////////// // 512 by 256 division. @@ -162,7 +91,8 @@ library Math { // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. - uint256 twos = denominator & (0 - denominator); + // Does not overflow because the denominator cannot be zero at this stage in the function. + uint256 twos = denominator & (~denominator + 1); assembly { // Divide denominator by twos. denominator := div(denominator, twos) @@ -205,15 +135,14 @@ library Math { */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); - if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** - * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded - * towards zero. + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ @@ -256,12 +185,12 @@ library Math { function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); - return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); } } /** - * @dev Return the log in base 2 of a positive value rounded towards zero. + * @dev Return the log in base 2, rounded down, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { @@ -309,12 +238,12 @@ library Math { function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); - return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); } } /** - * @dev Return the log in base 10 of a positive value rounded towards zero. + * @dev Return the log in base 10, rounded down, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { @@ -358,12 +287,12 @@ library Math { function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); - return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); } } /** - * @dev Return the log in base 256 of a positive value rounded towards zero. + * @dev Return the log in base 256, rounded down, of a positive value. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. @@ -401,14 +330,7 @@ library Math { function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); - return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); } } - - /** - * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. - */ - function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { - return uint8(rounding) % 2 == 1; - } } From a793750a7c8fe1b81b53374fbddfcccc7e0edbf0 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Thu, 21 Sep 2023 09:07:03 +0200 Subject: [PATCH 116/125] Applying fix patch --- .../token/ERC20/extensions/ERC4626.sol | 75 ++++++++----------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/ERC4626.sol b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/ERC4626.sol index 663377d..16656bd 100644 --- a/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/ERC4626.sol +++ b/lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/ERC4626.sol @@ -17,28 +17,48 @@ import "../../../utils/math/Math.sol"; * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this * contract and not the "assets" token which is an independent contract. * - * CAUTION: When the vault is empty or nearly empty, deposits are at high risk of being stolen through frontrunning with - * a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may - * similarly be affected by slippage. Users can protect against this attack as well unexpected slippage in general by + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by * verifying the amount received is as expected, using a wrapper that performs these checks such as * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + * * _Available since v4.7._ */ abstract contract ERC4626 is ERC20, IERC4626 { using Math for uint256; IERC20 private immutable _asset; - uint8 private immutable _decimals; + uint8 private immutable _underlyingDecimals; /** * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ constructor(IERC20 asset_) { (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); - _decimals = success ? assetDecimals : super.decimals(); + _underlyingDecimals = success ? assetDecimals : 18; _asset = asset_; } @@ -65,7 +85,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { * See {IERC20Metadata-decimals}. */ function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { - return _decimals; + return _underlyingDecimals + _decimalsOffset(); } /** @dev See {IERC4626-asset}. */ @@ -90,7 +110,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** @dev See {IERC4626-maxDeposit}. */ function maxDeposit(address) public view virtual override returns (uint256) { - return _isVaultHealthy() ? type(uint256).max : 0; + return type(uint256).max; } /** @dev See {IERC4626-maxMint}. */ @@ -179,44 +199,14 @@ abstract contract ERC4626 is ERC20, IERC4626 { * would represent an infinite amount of shares. */ function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); - return - (assets == 0 || supply == 0) - ? _initialConvertToShares(assets, rounding) - : assets.mulDiv(supply, totalAssets(), rounding); - } - - /** - * @dev Internal conversion function (from assets to shares) to apply when the vault is empty. - * - * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it. - */ - function _initialConvertToShares( - uint256 assets, - Math.Rounding /*rounding*/ - ) internal view virtual returns (uint256 shares) { - return assets; + return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); } /** * @dev Internal conversion function (from shares to assets) with support for rounding direction. */ function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { - uint256 supply = totalSupply(); - return - (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding); - } - - /** - * @dev Internal conversion function (from shares to assets) to apply when the vault is empty. - * - * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it. - */ - function _initialConvertToAssets( - uint256 shares, - Math.Rounding /*rounding*/ - ) internal view virtual returns (uint256) { - return shares; + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); } /** @@ -262,10 +252,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { emit Withdraw(caller, receiver, owner, assets, shares); } - /** - * @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares. - */ - function _isVaultHealthy() private view returns (bool) { - return totalAssets() > 0 || totalSupply() == 0; + function _decimalsOffset() internal view virtual returns (uint8) { + return 0; } } From 089de79c0106527e339d90c85a2623fdd8d405b0 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Thu, 21 Sep 2023 11:26:00 +0200 Subject: [PATCH 117/125] Prooving fixed version of inflation attack --- .../certoraRun-InflationAttack-Bug.conf | 17 ++ .../certoraRun-InflationAttack-Fix.conf | 17 ++ .../certoraRun-InflationAttack.conf | 5 +- .../ERC4626-InflationAttackMinimal.spec | 169 ++++++++++++++++++ .../ERC4626-InflationAttack.spec | 28 +-- 5 files changed, 208 insertions(+), 28 deletions(-) create mode 100644 lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Bug.conf create mode 100644 lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Fix.conf create mode 100644 lesson4_reading/erc4626/src/certora/specs-inflationAttack/ERC4626-InflationAttackMinimal.spec diff --git a/lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Bug.conf b/lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Bug.conf new file mode 100644 index 0000000..99deca5 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Bug.conf @@ -0,0 +1,17 @@ +{ + "files": [ + "src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC20Mock.sol", + "src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC4626Mock.sol" + ], + "verify": "ERC4626Mock:src/certora/specs-inflationAttack/ERC4626-InflationAttackMinimal.spec", + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "prover_args":[ + "-smt_hashingScheme plainInjectivity", + "-solvers [yices,z3]" + ], + "server": "production", + "solc": "solc8.20", + "rule_sanity": "basic", + "send_only": true, + "msg": "Verification of ERC4626 OpenZeppelin", +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Fix.conf b/lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Fix.conf new file mode 100644 index 0000000..421728d --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Fix.conf @@ -0,0 +1,17 @@ +{ + "files": [ + "src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC20Mock.sol", + "src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC4626Mock.sol" + ], + "verify": "ERC4626Mock:src/certora/specs-inflationAttack/ERC4626-InflationAttackMinimal.spec", + "link": ["ERC4626Mock:_asset=ERC20Mock"], + "prover_args":[ + "-smt_hashingScheme plainInjectivity", + "-solvers [yices,z3]" + ], + "server": "production", + "solc": "solc8.20", + "rule_sanity": "basic", + "send_only": true, + "msg": "Verification of ERC4626 OpenZeppelin", +} \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf index 7d8ada1..f5248c7 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf @@ -1,7 +1,7 @@ { "files": [ - "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", - "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" + "src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC20Mock.sol", + "src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC4626Mock.sol" ], "verify": "ERC4626Mock:src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec", "link": ["ERC4626Mock:_asset=ERC20Mock"], @@ -16,4 +16,5 @@ "send_only": true, "msg": "Verification of ERC4626 OpenZeppelin", "optimistic_loop": true, + "rule": ["simpleVersionOfInflationAttack"] } \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs-inflationAttack/ERC4626-InflationAttackMinimal.spec b/lesson4_reading/erc4626/src/certora/specs-inflationAttack/ERC4626-InflationAttackMinimal.spec new file mode 100644 index 0000000..25534e0 --- /dev/null +++ b/lesson4_reading/erc4626/src/certora/specs-inflationAttack/ERC4626-InflationAttackMinimal.spec @@ -0,0 +1,169 @@ +using ERC20Mock as _ERC20; + +methods { + function totalSupply() external returns uint256 envfree; + function balanceOf(address) external returns uint256 envfree; + function allowance(address, address) external returns uint256 envfree; + function totalAssets() external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + function _ERC20.totalSupply() external returns uint256 envfree; + function _ERC20.balanceOf(address) external returns uint256 envfree; + + //Summarizing complicated math logic by a simple version. + function Math.mulDiv(uint256 x, uint256 y, uint256 denominator) internal returns uint256 => mulDivSummary(x,y,denominator); +} + +////////////////////////////////////////////////////// +// // +// CVL summary functions below // +// // +////////////////////////////////////////////////////// + +function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint256 { + return require_uint256(x*y/denominator); +} + +////////////////////////////////////////////////////// +// // +// CVL rules below // +// // +////////////////////////////////////////////////////// + +/** +* This rule performs a deposit followed by a redeem. The property to check is that the step of actions, +* a user (attacker) cannot gain any assets +*/ +rule simpleVersionOfInflationAttack(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_ownver) { + env e; + safeAssumptionsERC4626(); + address attacker = e.msg.sender; + + //The following 4 lines are not required, but are used to get a simpler counter-example. For a full proof they must be removed. + require(balanceOf(attacker) == 0); + require(balanceOf(deposit_receiver) == 0); + require(balanceOf(redeem_receiver) == 0); + require(balanceOf(redeem_ownver) == 0); + + require(attacker != currentContract); + + uint256 shares = deposit(e, assets, deposit_receiver); + uint256 receivedAssets = redeem(e, shares, redeem_receiver, redeem_ownver); + + assert(receivedAssets <= assets, "The attacker gained more assets than deposited."); +} + +////////////////////////////////////////////////////// +// // +// Helper functions below // +// // +////////////////////////////////////////////////////// + +//A helper function that can be use to require all invariants proven for ERC4626. +function safeAssumptionsERC4626(){ + requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC4626; + safeAssumptionsERC20(); +} + +//A helper function that can be use to require all invariants proven for ERC20. +function safeAssumptionsERC20() { + requireInvariant sumOfBalancesEqualsTotalSupplyERC20; + requireInvariant singleUserBalanceSmallerThanTotalSupplyERC20; +} + +//A helper function that can be use to ensure that the mirror of balances for ERC20 and ERC4626 are correct. +function balaceMirrorsAreCorrect(address x) { + requireInvariant mirrorIsCorrectERC20(x); + requireInvariant mirrorIsCorrectERC4626(x); +} + +////////////////////////////////////////////////////// +// // +// Ghost definitions below // +// // +////////////////////////////////////////////////////// + +ghost mathint sumOfBalancesERC20 { + init_state axiom sumOfBalancesERC20 == 0; +} + +ghost mathint sumOfBalancesERC4626 { + init_state axiom sumOfBalancesERC4626 == 0; +} + +//A mirror of all balances for ERC20 +ghost mapping(address => uint256) balanceOfMirroredERC20 { + init_state axiom forall address a. (balanceOfMirroredERC20[a] == 0); +} + +//A mirror of all balances for ERC4626 +ghost mapping(address => uint256) balanceOfMirroredERC4626 { + init_state axiom forall address a. (balanceOfMirroredERC4626[a] == 0); +} + +ghost mathint userBalanceERC20 { + init_state axiom userBalanceERC20 == 0; +} + +ghost mathint userBalanceERC4626 { + init_state axiom userBalanceERC4626 == 0; +} + +////////////////////////////////////////////////////// +// // +// Hooks definitions below // +// // +////////////////////////////////////////////////////// + +hook Sstore _ERC20._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalancesERC20 = sumOfBalancesERC20 + newValue - oldValue; + userBalanceERC20 = newValue; + balanceOfMirroredERC20[user] = newValue; +} + +hook Sload uint256 value _ERC20._balances[KEY address user] STORAGE { + //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to already hold. + require to_mathint(value) <= sumOfBalancesERC20; + require value == balanceOfMirroredERC20[user]; +} + +hook Sstore ERC4626Mock._balances[KEY address user] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalancesERC4626 = sumOfBalancesERC4626 + newValue - oldValue; + userBalanceERC4626 = newValue; + balanceOfMirroredERC4626[user] = newValue; +} + +hook Sload uint256 value ERC4626Mock._balances[KEY address user] STORAGE { + //This line makes the proof work. But is this actually safe to assume? With every load in the programm, we assume the invariant to hold. + require to_mathint(value) <= sumOfBalancesERC4626; + require value == balanceOfMirroredERC4626[user]; +} + +////////////////////////////////////////////////////// +// // +// Invariants definitions below // +// // +////////////////////////////////////////////////////// + +invariant sumOfBalancesEqualsTotalSupplyERC20() + sumOfBalancesERC20 == to_mathint(_ERC20.totalSupply()); + +invariant sumOfBalancesEqualsTotalSupplyERC4626() + sumOfBalancesERC4626 == to_mathint(totalSupply()); + +invariant singleUserBalanceSmallerThanTotalSupplyERC20() + userBalanceERC20 <= sumOfBalancesERC20; + +invariant singleUserBalanceSmallerThanTotalSupplyERC4626() + userBalanceERC4626 <= sumOfBalancesERC4626; + +invariant mirrorIsCorrectERC20(address x) + balanceOfMirroredERC20[x] == _ERC20.balanceOf(x); + +invariant mirrorIsCorrectERC4626(address x) + balanceOfMirroredERC4626[x] == balanceOf(x); + + diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec index 5cbeee4..b558d29 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec @@ -38,30 +38,6 @@ function mulDivSummary(uint256 x, uint256 y, uint256 denominator) returns uint25 } -/// @title the loss while redeeming is limited -/// - better to redeem at once -/// - difference should be at most two -rule userLossIsLimited(address user, address receiver, uint256 s1, uint256 s2) { - env e; - require(receiver != currentContract); - require(user == e.msg.sender); - require(totalAssets() > 0); - require(totalSupply() > 0); - uint256 shares = require_uint256(s1 + s2); - require(shares <= balanceOf(e, user)); - require(shares <= totalSupply()); - - storage init = lastStorage; - - mathint redeemed1a = redeem(e, s1, receiver, user); - mathint redeemed1b = redeem(e, s2, receiver, user); - mathint redeemed2 = redeem(e, shares, receiver, user) at init; - - assert(redeemed2 >= redeemed1a + redeemed1b); - assert(redeemed2 <= redeemed1a + redeemed1b + 2); -} - - rule simpleVersionOfInflationAttack(uint256 assets, address deposit_receiver, address redeem_receiver, address redeem_ownver) { env e; safeAssumptions(); @@ -74,10 +50,10 @@ rule simpleVersionOfInflationAttack(uint256 assets, address deposit_receiver, ad require(attacker != currentContract); + uint256 shares = deposit(e, assets, deposit_receiver); - - //In the inflationAttack there are 2 steps that we don't model here! uint256 receivedAssets = redeem(e, shares, redeem_receiver, redeem_ownver); + assert(receivedAssets <= assets); } From ca54ce16ad56a048ffa02abecbc53f230ad735b8 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 22 Sep 2023 10:19:23 +0200 Subject: [PATCH 118/125] current state --- .../certoraRun-InflationAttack.conf | 5 ++--- .../ERC4626-InflationAttack.spec | 13 ++++++++++++- .../src/certora/specs/ERC4626-InflationAttack.spec | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf index f5248c7..7d8ada1 100644 --- a/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf +++ b/lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf @@ -1,7 +1,7 @@ { "files": [ - "src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC20Mock.sol", - "src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC4626Mock.sol" + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol", + "src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol" ], "verify": "ERC4626Mock:src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec", "link": ["ERC4626Mock:_asset=ERC20Mock"], @@ -16,5 +16,4 @@ "send_only": true, "msg": "Verification of ERC4626 OpenZeppelin", "optimistic_loop": true, - "rule": ["simpleVersionOfInflationAttack"] } \ No newline at end of file diff --git a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec index b558d29..270156e 100644 --- a/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec @@ -83,6 +83,10 @@ rule vulnerableToInflationAttack(address attacker, address victim, address depos mathint assetsAttackerPreAttack = to_mathint(oneEther) + to_mathint(oneWei); uint8 ERC4626decimals = decimals(); uint8 ERC20decimals = __ERC20.decimals(); + + + require(ERC4626decimals == 10); + require(ERC20decimals == 10); require(attacker != currentContract); require(attacker != __ERC20); @@ -105,9 +109,16 @@ rule vulnerableToInflationAttack(address attacker, address victim, address depos require(balanceOf(redeem_receiver) == 0); require(balanceOf(redeem_ownver) == 0); + //These are fair assumptions on the addresses. + require(attacker == deposit1_receiver); + require(attacker == redeem_ownver); + require(attacker == redeem_receiver); + //It is important that deposit2_victim_receiver is not equal to attacker, as otherwise the deposit by the victim has to transfer assets to the attacker. + //This would mean the victim already trusts the attacker. Interstingly, we could find a CEX for this case. + require(deposit2_victim_receiver != attacker); require(balanceOf(attacker) + balanceOf(victim) + balanceOf(deposit1_receiver) +balanceOf(deposit2_victim_receiver) +balanceOf(redeem_receiver) + balanceOf(redeem_ownver) <= to_mathint(totalSupply())); - require(__ERC20.balanceOf(attacker) + __ERC20.balanceOf(victim) + __ERC20.balanceOf(deposit1_receiver) +__ERC20.balanceOf(deposit2_victim_receiver) +__ERC20.balanceOf(redeem_receiver) + __ERC20.balanceOf(redeem_ownver) <= to_mathint(__ERC20.totalSupply())); + require(__ERC20.balanceOf(currentContract) + __ERC20.balanceOf(attacker) + __ERC20.balanceOf(victim) + __ERC20.balanceOf(deposit1_receiver) +__ERC20.balanceOf(deposit2_victim_receiver) +__ERC20.balanceOf(redeem_receiver) + __ERC20.balanceOf(redeem_ownver) <= to_mathint(__ERC20.totalSupply())); uint256 before_step_1_totalSupply = totalSupply(); diff --git a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec index 7094279..bd021f3 100644 --- a/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec +++ b/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec @@ -59,6 +59,8 @@ rule simpleVersionOfInflationAttack(uint256 assets, address deposit_receiver, ad //Source: Medium Article by Shao https://tienshaoku.medium.com/eip-4626-inflation-sandwich-attack-deep-dive-and-how-to-solve-it-9e3e320cc3f1 rule vulnerableToInflationAttack(address attacker, address victim, address deposit1_receiver, address deposit2_victim_receiver,address redeem_receiver,address redeem_ownver ){ + + //Doesn't work properly...Retry later. /*requireInvariant sumOfBalancesEqualsTotalSupplyERC4626; requireInvariant sumOfBalancesEqualsTotalSupplyERC20; @@ -99,6 +101,18 @@ rule vulnerableToInflationAttack(address attacker, address victim, address depos require(balanceOf(deposit2_victim_receiver) == 0); require(balanceOf(redeem_receiver) == 0); require(balanceOf(redeem_ownver) == 0); + + //These are fair assumptions on the addresses. + require(attacker == deposit1_receiver); + require(attacker == redeem_ownver); + require(attacker == redeem_receiver); + //It is important that deposit2_victim_receiver is not equal to attacker, as otherwise the deposit by the victim has to transfer assets to the attacker. + //This would mean the victim already trusts the attacker. Interstingly, we could find a CEX for this case. + require(deposit2_victim_receiver != attacker); + + require(balanceOf(attacker) + balanceOf(victim) + balanceOf(deposit1_receiver) +balanceOf(deposit2_victim_receiver) +balanceOf(redeem_receiver) + balanceOf(redeem_ownver) <= to_mathint(totalSupply())); + require(__ERC20.balanceOf(currentContract) + __ERC20.balanceOf(attacker) + __ERC20.balanceOf(victim) + __ERC20.balanceOf(deposit1_receiver) +__ERC20.balanceOf(deposit2_victim_receiver) +__ERC20.balanceOf(redeem_receiver) + __ERC20.balanceOf(redeem_ownver) <= to_mathint(__ERC20.totalSupply())); + uint256 before_step_1_totalSupply = totalSupply(); uint256 before_step_1_totalAssets = totalAssets(); From 169add5684be074390fe9daad7397e3fafaad961 Mon Sep 17 00:00:00 2001 From: Johannes Spaeth Date: Fri, 22 Sep 2023 10:32:00 +0200 Subject: [PATCH 119/125] Removing zeppelin specs --- lesson4_reading/erc4626/TrialsWritingSpecs.md | 120 +++++ .../erc4626/TrialsWritingSpecs_Johannes.pdf | Bin 0 -> 2102468 bytes .../certoraRun-InflationAttack-Bug.conf | 17 - .../certoraRun-InflationAttack-Fix.conf | 17 - .../certoraRun-FunctionalAccountingProps.conf | 14 - .../certoraRun-InflationAttack.conf | 19 - .../certoraRun-MonotonicityInvariant.conf | 14 - .../certoraRun-MustNotRevertProps.conf | 14 - .../certoraRun-OZ-Modular.conf-ignore | 16 - .../certoraRun-RedeemUsingApprovalProps.conf | 14 - .../certoraRun-RoundingProps.conf | 19 - .../certoraRun-SecurityProps.conf | 17 - .../ERC4626-InflationAttackMinimal.spec | 169 ------- .../ERC4626-FunctionalAccountingProps.spec | 131 ------ .../ERC4626-InflationAttack.spec | 185 -------- .../ERC4626-MonotonicityInvariant.spec | 146 ------ .../ERC4626-MustNotRevertProps.spec | 83 ---- .../ERC4626-RedeemUsingApprovalProps.spec | 118 ----- .../ERC4626-RoundingProps.spec | 132 ------ .../ERC4626-SecurityProps.spec | 76 ---- .../contracts/interfaces/IERC4626.sol | 230 ---------- .../contracts/interfaces/draft-IERC6093.sol | 160 ------- .../contracts/mocks/token/DummyERC20.sol | 16 - .../contracts/mocks/token/ERC20Mock.sol | 16 - .../contracts/mocks/token/ERC4626Mock.sol | 17 - .../contracts/token/ERC20/ERC20.sol | 372 ---------------- .../contracts/token/ERC20/IERC20.sol | 79 ---- .../token/ERC20/extensions/ERC4626.sol | 286 ------------ .../token/ERC20/extensions/IERC20Metadata.sol | 26 -- .../token/ERC20/extensions/IERC20Permit.sol | 60 --- .../contracts/token/ERC20/utils/SafeERC20.sol | 140 ------ .../contracts/utils/Address.sol | 159 ------- .../contracts/utils/Context.sol | 24 - .../contracts/utils/math/Math.sol | 414 ------------------ .../contracts/interfaces/IERC4626.sol | 230 ---------- .../contracts/interfaces/draft-IERC6093.sol | 160 ------- .../contracts/mocks/ERC20Mock.sol | 16 - .../contracts/mocks/ERC4626Mock.sol | 16 - .../contracts/token/ERC20/ERC20.sol | 372 ---------------- .../contracts/token/ERC20/IERC20.sol | 79 ---- .../token/ERC20/extensions/ERC4626.sol | 271 ------------ .../token/ERC20/extensions/IERC20Metadata.sol | 26 -- .../token/ERC20/extensions/IERC20Permit.sol | 60 --- .../contracts/token/ERC20/utils/SafeERC20.sol | 140 ------ .../contracts/utils/Address.sol | 159 ------- .../contracts/utils/Context.sol | 24 - .../contracts/utils/math/Math.sol | 336 -------------- .../contracts/interfaces/IERC4626.sol | 230 ---------- .../contracts/interfaces/draft-IERC6093.sol | 160 ------- .../contracts/mocks/ERC20Mock.sol | 16 - .../contracts/mocks/ERC4626Mock.sol | 16 - .../contracts/token/ERC20/ERC20.sol | 372 ---------------- .../contracts/token/ERC20/IERC20.sol | 79 ---- .../token/ERC20/extensions/ERC4626.sol | 258 ----------- .../token/ERC20/extensions/IERC20Metadata.sol | 26 -- .../token/ERC20/extensions/IERC20Permit.sol | 60 --- .../contracts/token/ERC20/utils/SafeERC20.sol | 140 ------ .../contracts/utils/Address.sol | 159 ------- .../contracts/utils/Context.sol | 24 - .../contracts/utils/math/Math.sol | 336 -------------- 60 files changed, 120 insertions(+), 6965 deletions(-) create mode 100644 lesson4_reading/erc4626/TrialsWritingSpecs.md create mode 100644 lesson4_reading/erc4626/TrialsWritingSpecs_Johannes.pdf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Bug.conf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-inflationAttack/certoraRun-InflationAttack-Fix.conf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-FunctionalAccountingProps.conf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-InflationAttack.conf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MonotonicityInvariant.conf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-MustNotRevertProps.conf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-OZ-Modular.conf-ignore delete mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RedeemUsingApprovalProps.conf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-RoundingProps.conf delete mode 100644 lesson4_reading/erc4626/src/certora/conf-openzeppelin/certoraRun-SecurityProps.conf delete mode 100644 lesson4_reading/erc4626/src/certora/specs-inflationAttack/ERC4626-InflationAttackMinimal.spec delete mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-FunctionalAccountingProps.spec delete mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-InflationAttack.spec delete mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MonotonicityInvariant.spec delete mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-MustNotRevertProps.spec delete mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RedeemUsingApprovalProps.spec delete mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec delete mode 100644 lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-SecurityProps.spec delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/IERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/interfaces/draft-IERC6093.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/DummyERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC20Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/mocks/token/ERC4626Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/ERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/IERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Metadata.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/IERC20Permit.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/token/ERC20/utils/SafeERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Address.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/Context.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/IERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/interfaces/draft-IERC6093.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC20Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/mocks/ERC4626Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/ERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/IERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/ERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Metadata.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/extensions/IERC20Permit.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/token/ERC20/utils/SafeERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Address.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/Context.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d5d9d4b-buggy/contracts/utils/math/Math.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/interfaces/IERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/interfaces/draft-IERC6093.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC20Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/mocks/ERC4626Mock.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/ERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/IERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/ERC4626.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/IERC20Metadata.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/extensions/IERC20Permit.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/token/ERC20/utils/SafeERC20.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/Address.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/Context.sol delete mode 100644 lesson4_reading/erc4626/src/openzeppelin-d64d7aa-fixed/contracts/utils/math/Math.sol diff --git a/lesson4_reading/erc4626/TrialsWritingSpecs.md b/lesson4_reading/erc4626/TrialsWritingSpecs.md new file mode 100644 index 0000000..64627e4 --- /dev/null +++ b/lesson4_reading/erc4626/TrialsWritingSpecs.md @@ -0,0 +1,120 @@ +--- +marp: true +--- + +# Inflation Attack on ERC4626 + +Goal: Use Certora to verify that OpenZeppelin's implementation of ERC4626 is _not_ vulnerable to [the inflation attack](https://tienshaoku.medium.com/eip-4626-inflation-sandwich-attack-deep-dive-and-how-to-solve-it-9e3e320cc3f1). + +--- + +## First Step + +Expressed a [CVL rule](https://github.com/johspaeth/tutorials-code/blob/johannes/openzeppelin/lesson4_reading/erc4626/src/certora/specs/ERC4626-InflationAttack.spec) describing the inflation attack. Rule [`vulnerableToInflationAttack`](https://prover.certora.com/output/53900/0bf85896db274ffaa69423bb9aee18a3?anonymousKey=0c379fab28b2c842757f5dc9ea0d80e8fe524fe0) shows a counter-example on an ERC4626 implementation (not the OpenZeppelin one), that is vulnerable to the inflation attack. + +Running the same rule on the OpenZeppelin implementation of ERC4626 [results in a timeout](https://prover.certora.com/output/53900/e5626ff6be1c42a6be6e3165b9e019b0?anonymousKey=5de6d6561925486182b6676849f546d19b180a4c). + +--- + +## Approach for Mitigation of Timeouts +* Issue: rule for the inflation attack is quite complicated. +* Idea: simplify the process by trying to fix a timeout on another rule and get back to rule for the inflation attack and hope that timeout has been resolved there as well. +* New rule chosen: `inverseMintWithdrawInFavourForVault`. Using the rule the solver finds a [CEX on the implementation not from OpenZeppelin](https://prover.certora.com/output/53900/644362c3b13b40ac9a2591261f47da73?anonymousKey=fc4c4d05380fd0a603b52841daff53be4bc1e96d) but [times out](https://prover.certora.com/output/53900/8490846494044992848c1133995bea66?anonymousKey=8f65f17f2f781c8c705b3ba03041848b600e7213) when verifying the OpenZeppelin implementation. +* Below is a set of trials to resolve the timeouts. All tests have been performed independently, i.e., state has been reset between runs to see individual impact. +* The order the trials are presented is not the order in which I tested them. Initially, I followed the dump page output and the coloring scheme to understand which methods are affecting the timeouts. `withdraw` and `mint` were labeled as complex due to the branching structure of `_update`. `mulDiv` and the underlying non-linear math wasn't the first candidate I looked at. + +--- +## Summarization (1 of 2) + +The function [`mulDiv`](https://github.com/johspaeth/tutorials-code/blob/johannes/openzeppelin/lesson4_reading/erc4626/src/openzeppelin-9ef69c0/contracts/utils/math/Math.sol#L122) of the Math library uses a lot of assembly code. Assumption, if we can support prover in reasoning about it, we hope to get rid of timeouts. + +* Replace the entire function by `return require_uint256(x*y/denominator)` → [No Timeout](https://prover.certora.com/output/53900/d27c1b91c1124a2a94ab218a786ad663?anonymousKey=1025554e844dde70385a9e77e4e01e32747d69a6) +* Replace by upper and lower limits, it holds that `x*y/denominator <= res && x*y/denominator > res - 1` → [No Timeout](https://prover.certora.com/output/53900/8532175924fa45ebbf0ffc5169be33a4?anonymousKey=e978c048cb901d0580f456f6f94b118cb911147f) +* Further relaxation of `require_uint256(x*y/denominator)` as `x == 0 => res == 0 && x <= denominator` (using domain knowledge: `shares <= totalSupply()` or `assets <= totalAssets()`) → [No timeout, but CEX not valid, as rule too imprecise.](https://prover.certora.com/output/53900/7ea1e2d3e730424a86039175967d109e?anonymousKey=c3be7ae883f01a12845d619da481e7364e6839d2) + + +--- +## Summarization (2 of 2) + +* Summarize `_update` Function of ERC20. The complexity of the `_update` function (>3 diamonds and many calls from `transfer`, `mint`, `burn` and `transferFrom`). The function modifies storage (transformation on `balances` mapping) but does not return any result. → [Timeout](https://prover.certora.com/output/53900/ccba8ccd72d948398b664653026b6506?anonymousKey=2be223f7795547c09daf65b18afbba1af022a225) + +--- +## Using Ghosts + +* For the rule we are expressing, we know that `convertToAssets` and `convertToShares` are both called exactly once. Using a ghost we can store the parameters to the first call and summarize the result of the second call using the stored value. We know that the following equation holds `assets >= convertToAssets(convertToShares(assets))`, i.e., when the second call is made we can use the ghosted first call's result to define an upper limit. This way we assume to avoid calling `mulDiv` in general, as the calls are intercepted. → [No timeout, but CEX not valid.](https://prover.certora.com/output/53900/9061f8f699f44f25ba82e107700c53f3?anonymousKey=6a49b2d92ba1e5aae72963fb12c768aa4d468479) + +--- +## Concretizing Inputs + +* Hardcode values for addresses. → [Timeout](https://prover.certora.com/output/53900/9a658882ff0546ada41483dddad87e43?anonymousKey=2df94692716083070411c00b761f4b96ca7d3bbd) +* Hardcode `shares` to one specific concrete value. → [Timeout](https://prover.certora.com/output/53900/be834272042a486bb1af4920959b972d?anonymousKey=4f24aaefdae632cdb09d3879ff8fe0fbc338025e) + +--- +## Munging + + +* Observation: The `_update` Function of ERC20 is central to the code and complex. It has 3+ diamonds and combines logic from `transfer`, `mint`, `burn` and `transferFrom` in one function. By in-lining the code, one can reduce code complexity for the solver. For instance, branches `if(from == address(0))` can be eliminated as of knowledge that `from != 0`. This requires inter-procedural (but not cross-contract) reasoning over path conditions. Does the static analysis eliminate these cases? → (Led to a Timeout. No link available - I can reproduce if required) +* Replacing [`safeTransfer`](src/openzeppelin-9ef69c0/contracts/token/ERC20/extensions/ERC4626.sol):278 calls by `transfer`. `safeTransfer` is a method from library `SafeTransferLib` and uses assembly code to perform the operations. → (Led to a Timeout. No link available - I can reproduce if required) + +--- + +## Experimenting with solver options + +Using options +``` +-smt_hashingScheme plainInjectivity -solvers [yices,z3] +``` + +→ [Timeout](https://prover.certora.com/output/53900/a7adb6e2c62846789c830f76b8765785?anonymousKey=58dca0bb32f089ee2b4cc23abf115c48d2d63d8c) + +→ In the end, the summary `require_uint256(x*y/denominator)` solved the issue. The additional SMT option wasn't required anymore. + +--- + +# Non-timeout related comments + +--- + +## Limited expressiveness of CVL + +* Expressiveness for summation is restricted. For ERC 4626, the equation `sumOfBalance == totalSupply` is not expressible. A [complicated rule](https://github.com/johspaeth/tutorials-code/blob/johannes/openzeppelin/lesson4_reading/erc4626/src/certora/specs-openzeppelin/ERC4626-RoundingProps.spec#L61) is needed to express the formula + +$$ +totalSupply = \sum_{a \in uniqueAddresses} balanceOf(a) +$$ + +where $uniqueAddresses$ is the set of addresses participating in the transaction. + +Is it possible to express a set and containment in CVL? + +--- + +## Learnings on rule writing process + +* Setting up [a GitHub Action](https://github.com/Certora/CertoraInit/blob/master/.github/workflows/certora.yml) significantly helps to keep track of changes in the process: One commit equals one Certora run. +* _Improvement to the GitHub Action_: Certora could report back to GitHub. Then it'll be easier to reason which commit led to an error (failed verfication/timeout). Could be easily done, for instance, using [SARIF](https://sarifweb.azurewebsites.net/). + + +--- + +## Open Questions / Other comments +* How does a good structure of the rule writing process look like? +→ _Will probably learn it in the the project, I'll be starting this week._ +* During the process I frequently applied combinations of the above trials. My assumption was that it could have easily been the case, that only _in combination_ they solve the timeout issue. During the process of rule writing, how can one judge the best approach to take to not waste time? +* When using summaries that are too weak, how to avoid counter-examples of non-interest? → _...most certainly hard to guide the solver into a certain direction._ + +--- + + +TODO: Get back to inflation attack. [Run](https://prover.certora.com/output/53900/9c2969d79a5a4873988750f7625e9e66?anonymousKey=be24280a64b9a374ea1d76a8b4f38b52169d24e6) with basic `mulDivSummary` doesn't timeout. Inspect results. If the CEX is correct, open Zeppelin would be vulnerable to the attack. +First observation (needs second inspection): The CEX doesn't work, the global setup is incorrect as property `ERC20.totalsupply == sumOfBalances` is violated. Must be hard-coded as it's not possible to use a `requireInvariant`. + +---- +## Improvement to the rule writing process +When writing and testing specs I had several versions of the spec and code at the same time and submitted those to the server. Because I had wait several minutes for the results, frequently, I already initiated a job for another idea to test in parallel. The code and spec were maintained using git and tracking changes of the code diffs are easy, but mapping back Certora results to the actual commit is difficult, especially as one had to remember which link of a Certora job belongs to which commit. + +I [found a GitHub Action](https://github.com/Certora/CertoraInit/blob/master/.github/workflows/certora.yml) that I added to my repository. However, this GitHub action also only triggers the actual run - **no indication on GitHub is given if the verification actually terminates successfully or not**. The feedback one gets on GitHub is limited to the successful _submission_ of the job in Certora. I wish I could quickly see the list of commits and the actual results of the Certora execution, such that one quickly see which changes introduced / eliminated the timeout for a rule. + +Certora should report back to GitHub as part of the GitHub action. See SARIF. + +--- diff --git a/lesson4_reading/erc4626/TrialsWritingSpecs_Johannes.pdf b/lesson4_reading/erc4626/TrialsWritingSpecs_Johannes.pdf new file mode 100644 index 0000000000000000000000000000000000000000..416fe4f4dda47ea535110d2790f19b70c1ba402d GIT binary patch literal 2102468 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�gq4iZna&R->An%%woRt-Ye->HN4hX&eUvG2wAzaAK_Ee-AbY1;5C;Hog6Xm5*x!V=8L3uFaPu2sO6u zy*-rH0Rg1TlE&Ayi6{~|uo#YO1EAQ~?#{w5+6YtI?M)r!*cGmDeA#L?NFI)+C^&##RhRb6o|dR52WW}*IBS_h43gc z*)#tnCR$E(x$mmLy$o};pai7sY_(0 zZzVz6a2(CM5#gL9fpX*s%zUln)b7FLT6)a5%cKG38%Rx!g-;SOWt$BaP1Af*5t@r+XZ!p^*=CKl6|_Y}s5VYAWdFb@2-tD=}YSiAX5L!)3F zdgoANW>XQKu6UxA$9knd?RfTBh3zSiM%cD&h|-OyMF8bzA!JjaG|!b%>Z?PrIi+8# ziH>p5d;{qdP(MAa@X=<-9*OT`T%wL|1YG56kS%h7@h-a9t}z?oSP48@0mV$>?~@Ir zcayLJuoDS9qMn`)$1p?<^qTnD1R7(LgHMF?5R<3NE9vCbGaZ$j<1BZ7o; zh`oq2JvDyO%x2s-Q|>aHJLe4rDtqiXgqcmuEE9el#PO+A?(t`a#9brsAU(%c+pGWZ zgF21QHJVmaxG2o!yd0yY%y7raZ|wfFOwFAB;CZ2O=RqI@xfz?5h$nyqjKA6!9jfVQ zdQy)yVkD-t1F|7!-+}ON)245CH%id>;u?X5zz<|4iX@?LC53FGasm{7u%nY<)<{HNTm%Moqifa$KDu{yb03PD^@3klNx=@aqzwQXBCf$f0F#+g6tI z&^-2ko@7ROQIPVJWr{7ROEJ@(JBD3iHMD08F47)0X#cJ@JCj(YbFt5KvEeyb_|gRC zTwGmWx*cXkw@cm2zQ;Pa_GE5~Zn_x!r`-m&e0C&_a^*vnPMEwyoOorXii=H$CRfb5 z;tr8ODHQT}^SChEfmv*VBai!4EBA1()NZh&$YwSs685(D5O_-c#x$H*r-G=;md3rB z2}9&_M>sc&>#4T4?7c+H&Ei7D2#L$h;zIbmA?SRvxM4iKppv*LGXr3`*M5CqDw31* z0wiv7?F!wJm`98u(uQhcASH39&0ORMpgTscHS$^d$m!LS!$nS6zX-(_og6O2Z6m8# zO5&!xVXA3*-7GgIvoQRu?P-&^EnjZMwAm|Vtv8W9zG$t!LU7SKD)fmw71|Z0==mmb zVfHf`6M9Y(x1xz+?vPF5PN_wOpb_tAJD5nOR``9AxDe!Q(fJc23NP{|aSx_1#^)q) z-*$}uByk~Xu+larI*JSeSmdU0VU9U$igXHio8iueN{9hx%IbDjZFPbKfsw_oAL6BK z6h05)Q;i69)D{jdq+FYNwH8Pgce`54Yo#hwn|>yX3sBl;q4YxTBGKTLbY@WTDT!kph#BU<^13G!!MP_6+RuD9qoSWc`e$%)R$CaDUo5qES zwwt$wm^7{#Dbq}WH~rN&wtAwdf7vg(jSkwhM7~?bB9V#nAJ?trD{8z*a%I`A366bQ z;TENNjY&t7#f{44AXl*Jw9d|I6}?rr5ca#bF8`%WF4G;V+|h_>kIBWF*VZdC`9f;ws3 zna5Gno5nR;#jK5pyR{z(Okmi6q}U4hztJM9YC|5g@PqL%?ukj%wQyk538y8a0vqq{ zVqYP?927Td)n{4dXR`54h#+i{V;ILTU6pX;phJ*{MhX14yb3@t7)g$i!DYH&!0Y!YVWy#9_YUPvV#5(5v`CbGW z7HTByFnPtB_I6qlZiOn|L0r~8p%x6gZlsNamTY#ddhT-YPn>!H5ye_O#gU6Js`@(nQT(|LNiV~Fn%vPQJnbae-sUhb@n zike~!gSvXT8LLlOMS1ixLfPugM6X^pzcRbuRBi;GyT-Md3W-L}rxa|=bAw4fk-fJg zO*+h&!}VbXl+97P>G&26eE~Li>gNu-`xVaCVRIYt7&pUFhJVBm_T1dVxd9 zr1NR?Mgs@K7&$zcRzb;hzH7%TyzigoJ=}Q*nds*!Hte0pVH9NB*?woILz1JG57pWc zdfD-{!XF}($y-%7o>U4bC{2u zL3|d? zZs5z)2kE)uoY^mXS}_MJ3-v^KCmnmDu=nkEvC<@cj|C5VaTNg};+675Mn3zokS>uK z4&Lu#C7&Xh)+z$LWO+ zD>Ro0Hy(B|N;o#vff4kMT|Mu_yEx`Qm zFI<-bPM9cxHeC4A|7~$P6dUx24w-h|Q2!wsX_Q$Dc zQY5;Mg{(DSH|1%B*RPvmm`j5{#a^f6JWB$8!Z7^nlyr=DJiR}~KA!cbWXAXY7SrFy z_@4g$1jFUmQ9185-Rr18TydHbypD>~wAdtSi(`(X(&6N}ploz{j*6M0NUqCuRDNRB zPY@C>W4wV}fmwxgW*8Iv2hTNQo#D98B2t3q4L z)$5){SeDbb9g$sKTQ^_iAqknQNsd@t%ce~UJ1u&60%A$9V%FH`7O-?BK?A(ZXJVl5 z%=YdqJS_XM+wfpR{DMeX7dr1K%Z?z(Oxo9D)iinJBE*-Y;(sc2i`feKC;h|?DvgQc z)fM?Vp+8P$QU>Yn6bN}Y*@>-(PqjIz$vU-rTj- zwxe+`XZ^-%9_tFG4Wue->!i~dyWk(aeJCo>u5p07r9GLudW2#rqex6I^M zh`6TbP;N7mVG_hWcQTV9T#Srnj(S}4B1B1+Y~=H4^<*YL+ObtLnCU(1PWmS^SsMwH zMaoQ`z8qRf)M$jR7Ib4ceErxXl{7|e$2rN!%}(xAj;bzQGO{h(w(E70DhZ7mo+LTNngWY*b2otn zn-goJfW8@!b(uzB=U}YI*p$TX9?3J2=VvRo8M60@gT-Me<}A~U!2JIMKpMI$Q*lEv zQ2}VVr;L&z8 zO-{z<<8^}5t?GORl`k6G=f6xv{tGm@8RUhQ0>iYYn}J{Di4vWEpOu)SSP@w?zao8(Vuh8AXi+94SOfve&Jl>M^WCGY!;HtgnzN{bPhViS+<10M9bV`h|vS_O4aq z=pRjq(fPRq+jCeKJFlst#rC$+v>k8fum({`+MD@Ndk(7?`3yrOlllFlcbJ2H&YrvK z)XeGH#NZ%tajH2V>td0$GC(~wTAFHzU)rF)&pE7?NF`(oNoh*9`$NL?mM;u3vBP}A zwDpnB85zB7bFirJuu|{4O6M})pJ7v!Gj4KaPGjMhNBh*uc$G`t^UJGFux(4wSVuxH z<{6Ae!Yz~(TdSKUjVk zM82h!)oLn)qQc50q^rjExq~r&Z5>#wp6nYgn1sb4AOJK^QiLoMXf~*=A%k!@cjU@W z8)tFNjDQgC-gZcb9zEuE^h9-$k)9mRNv0nxYKjq;C#QH+g`C{!0;;?SokgIL*F7wC zlZwD7wqlob0&5}w*-N!z+YS^NCd?;yYGthi+j_bT1`)y%xffx_w*>eD`(PmsY`tZ0 z9X-%4X!bG17&9C*#>~tV#}G3!Gcz-D%*@O&GegWw$BvmXrej#Y_ug-Jt9F0PNF#MU z-KA00JR`NbZ;10X7@8`wj||e@1i@fTo5djqFOq;PW?@7TA4-zp`1Ir^eP<`PTCPRb*IMS!s%ZVy~TzQiS$!8!q$eP!G98&qjPLhkOnF%kq)JS&HYL{l6j3 zz};D%B*?Wpv2w*NzaQCOR>;WYmYE~;aUV0^&OJTv#&NHiA2Vq?#-5WcohKeT`6Ox! zd)7BIz#SZ)9&zi~HMTMS$Dl(kQE=PH-b0uinU&YTx^rS;vz+h3zNOmC+V4km--Ac9 zeNw_nk5m*~(0ZvW!MO2qsg*xthJ*8l(dXt2VXT`JbVjL_&`_AEWZMqCv<2>Z*H@nnWo zrG>;amts=uj>c<7&wjP6%5co(q~-9did;`oZsodt%QQNCH89tLwD?M;Vv45B>Z6fx zNwSEiviy&!Toa!vm}z8b^g}FUqgt_(W4?7Ql`WrL79VPgh9w_@ceNE7?PsC96Tjup z8bJP|XsJza`&?!VZP^6=y-r1P6Q$~!APk@SS1_qb>Dome>b&!D=aGs!mO1(p8SgAu z2j4K~$q*^K@ojlJw(vezWbGU4FO3^A<=_5^IZS?C$elH_29po=e7+Lphl*E)2GVvu z8|ks66LX!~EhLSPL4BX2R?6DalTfi94`bCurXqt4SA>9Rd(?D4?7C_78f`gBW54uX zj|b*ityo9RLhWC+t9&4Ul^DDh85tIb`!3T=$G2YjMrFY!XmpTawu6xN?M5wECX*;> z=*KVofzhwUze|~U4H_!eucnFJc_eXVZR{T2#KZCh9_K0t-fTXT&OuJ(%3q1?d-P;* zhPjGOvQ9bSUF)3+AC=3M@=k@SPQl1s_(3Rd?z1AYPRylOrFwb;_mW{JnH%DLunNUkmmjnRbO6dx)tF8M3x z7mguw1ubuB@DH|(ABRLR2rUCZlzU5w#NC7wOFC05QPz$@2`rgQbv716LtDu=>2jc47;42lFS2 zO();7lx~*1UQ}NQ5-oJh4GOeDOS(!jI;E}1U{O=3< zip_j7Tbi;c2f_grzZI{7yLDw4Ls1|mKI5u-@rG>tnT-^vLVte$Fq!^a#*BuZoYHth zKpA;4`5F|)_m>-9&HS4&13oI5tM<4vBF-E~Wwo;MQ}q`1TDLepMg-u%3XNa+Jydwg z7#KzDmVK??AuJRx=S#(%F8_y8QWJDKY;I*`JrR{MtEy_;R(3`)1a+RzVHJs+ZYXDqH4Kuyu4=lAR}v*u3c)Dhu)A|<5ciLYiD(9GbKzdY@EUzNwd#(_ zR`+OLzdS0w5H-)U%I!d-e-%|;)X?Z{EC}WkZRGl@%(G~WhJUMMOeMS8_3#^6W+qoZ ze|nX?{+73rIDH~D|bveuO=TN>Ai_Um*P%TY#*C9t%$KO~^y z(AUQ`{|SmR3n{wL)fovK^GHmMHXsaTZ`4OQwd8;$|917mS3SW5;N|rbq}##aE);Z# z&B}LebGkpf;9AokoImlyj|917gyiQ@zL)2C6lreW+__1wel=I>o10SlwS72$YeML` zNE|B(HTdF(zw^*CdlbxsUsjlo%8V|i;qC7Y+Nj_d+vMR~)Q7Gb)``8Yd8OnxJ4inQ zoZ#r*)`S+h9;Uw>+gzV0V|-A@;d=V>y5MvGVl4WG10sFT)`9k z{Do1BM+-_~U-5ln!cFCQ#MD{g5I+U-aN`N3`9^{yYrGVeNIq@}qgfdFFb(c*Nzh*T zD#FL>UU?y+x3p?gPiSu!enSM(t+zz)_e#|$bTEmtS{UaiUlGK}&Mk~Xz8~?QG;SVZ zlqp^e-wC}BP*ZO_viBuWf_t)AzJK$E>89Sm`MV)W&vo=GjydkIal9CeAG38u5&2fv zClHd$SKZv|!an!>K1*@ZQ9>R>dIs+;ar_zTmKWIZ`6(be>(^rQqA9?N{WX%PEBw5+ zdi!by9ypF8QXUs1F`OX~Yt-m5(T@Onn7dyd*As1T`yuB>((Z&T?w7t)zMhKBoZSBO zl@VUW`qwA>59L|O6>6EJ{$F6OivC`6qMCo!DVs5)bQ(|i6fDsnjR#YBFDGBaY^qpR z?+Dz2!&Tj}^zMSvuFa~MFUiwKm>@NTEt}%w3Ke~N(?_G0?a~*m*+%-JS*j&>9tTOD ziXPN`nSb55)Hce#I0GG7r%36WKZPrLwy%`YJZVgfm3`StFQ1VKMp?x}h=c7bQFpHw z_B}D87T>1bhq55)=Wu?6eDRRaBG;3#PJEYmFQFdVN|sQlnh&HeJ$Mc~DF5wwy8k?| zh#Zo`uRo>_JdAqo%-thfPiIieX0fXp=A3z~+1h_Dbyqh#uEIs$2&<0H4DZb7k*r;% zP~|XGteG;cQx?eRvDlfG-OXN{-L(PZb z6O=|NaS=qf0+7h5D^wMDkKB3QmOhu>(dxBZ3PwORc#R^=`z$P6c~^%uo5 z`N+My9hAi=`XAbp5AQs24F~sS6f&7i>a_mpSPBXxHnii?n#}u+9oy!IFM+>t{t_l+$E z!aj2*^1=-Z`J(u_v=`nxPxbYnRQu06%nR<;-M$gXi)|@+-y0MqoxG2fXdquL|4YG+ zTrkoi>mUnOI#s;Irt$B2>GLKZ8(zlpF+mwIoY{m0v;wi&aNk;|opJjrkdgPr#K2Y~ z$M=$zdFs>6d6o4V2b*=Vf?F4E88fGNz;lBEx>s>ZmQRxhS78Si8o5h1mVTRmLYYf0 z<8k4TnOQFNfpv}50lFplZ|G#7mn@GzvjZWlawB-WFB>{b8GV z<7Hn3-yaHgK{?@Vj`@q3pgNeaB}QOEgh3%55};(+y~kSuSs8t^yiIX&0gIU6KW)1L z69r=Usc7aB_tH}UWpmjWsmbuJ9;;Ha!c$An^~q{Giza`;A-lwrpqhnR{ay`0Jk2F6 zvEe(uqqe<+Ln<3@^H#xz@_Lo3W13p(DMSAwZu zvkuK!4+wLu3;+SiZ0wQm&zIpAFBes9`wZrjSe*^}`N}wf#WTI~XiY7UEcB`?eRh2i z&%Ws`2#y^tA%)uGj%c+GQ!g9)5V>lv&9MjeeFiO?K_p9-e=ATdAan2v@?$Ka!v>u( zMQ!>V&}_E@cKe^)sSP{|Fqt7%t!VgBQizg@ zzq#LTj5*sQ0R`_%{JbNwzGhJ=^KK1}|R0VNX-PUJS~(fm&m5W(V?)!V$Vdt=#!1 zvrLwoTowUy%K~meVa4C)TT4Lska%Ou*p33jkaqm?2+i2kRshEpg-wUTF#9n{l$RHa z&oteF^_UzXG?`U(m>g5MrEr{fZ5USzGA(na!;pVwB>N!<*dcV}*7vvbAytQbtld6s z`h1V&OU5qC?3&@hgn8dt^1DfmQtq<}Sq~CgKlj2Y6N&WZuFLa|u=KK)+ zf-h3bMmX~%{}SFP%j?GW5&DbCM?Ko)+-E;yQXA8=0@XkN2{(SXK)FL-dFJ)ffBkCwTnR-DzYBbX+LdK`} z?Vu~VX_`Q{u5_yT8)8QqUJQSeZY5hFMdqsQ3cGtQE<7kzbiC5MIXu5|RLXr{VvE`e+Mu zB9P#co^Rxm8i_@Vk@Kx#%;T0i#53&x=kGZX^vO#$y72dU)2656;#u*GRKYHKHYKfVSJ>%UJ2GYrbgbonKl=J$QIu~A5O#CE9#;QE&%0nDVvT{*Rvh9%ba_YT-42R!Fb;Wg#XD^tEZz?`rLdb zyCRS1o(q*DM|CE%539$RA4xEp4Vy-rb~lpV$NFHuC!z`ER8Jil?7H2LFxx=-5zZXT zEr;!e>t^ZhdKxbKBm&0x9=au9^$|TNQ^2uzQ~!ul3#6nQm$cq`@DKZ={BaZA!MJup z>yst_RsH88TbEHo&22PxOdSDYnYRC>EzG&G%+k8={rq?l+-Qkj$={a@{hX8edPe;pCmu~O?@i34K~*ZTf?5zt9<`- zV&NbOe?Pbq+ML@_gOe)tyAZ0-`#704cV~~-QhGl`YICUxt4m)aOfYPY;WxvFA?s`( zaboO1ajc|+>N@;9!}p9nZ29wRi;>Z-MDpa+kw~)$n&=YR?>XTf#3*Y}9CI;mo5=K8 zy#RW&Z3}O(Y{x|xQKeFbTqHN_S zIo!H>ri#l&zOn%CRQ4#0ijo^g%qvjWx%f(m)tv@E%RJMU=lbmUZ@E2^=pFQdiaPOb zhiGbN;_TvNYH0gk&ECigkp;*M{IBHa2QsO6I+y~P6b;S)*WJm~&IQQ&+5Hp9^wZSY z-qp$2)EUVBzZ=Bt?OZ*qkV(wm#@T{0~DL$o0QEb^iyP`~L+l|7j|dh>eS>lihz7F^QYHSsI)EloACp z$(h=jyI25OIayeNOp=!W$$XYJpJL*s#`Y$r|Ec;vqvipb^UGaDc?~;!rkksK7K02( zd5`af0G#KO{tBZMDHEUw6&6C}CJrl!g-)wxqS6N`6;5U>frS(tWVC{Ci>5fzuOu#U zuoYvCa8<)`x1ISmstH;({}+oE&pgk0W)!*O3NC?!irshP*_d2H&kcyPL~NVBl;lr0CrjFq83bwpKQ6Ui!pg?AZw3k3m48RF@3 z`V#%jW04EtQEI*)1rsS_RQj2}*YeGK%M-;3+1e$c+U(EEbu7 zmL&W>iGB2eYr>n`~9CDhWu?0Q{l^88G zlxk2{3%Mb1qG!c^8&WffH_)z6Yk*!gJm(|)GTMjtm(anl4OfTNj-wW)6?G-1GvI6x zWtZy4ql<1E>naGf-}&0ehqMRLpU|He6i+hJOPzqA1!EnAO8N^(VMC#Uh7ULbyoI0- zuqy?YE4Yw_p?MDJ7|}Mwt%`KX5K_q}W66Y3;QXYZ&1eo(k+CAvr4pd(CQTmaHIZ_{ z)fCoKsHMmQ>Qka72`Q$~P+=*^5=w}!$@9pz3wsKCik19CDYskHs7&w3(NgFZ_m%Z! zlKNa_yGz>}uSu>-v`#%s^QKftSxIwG(`U@1)+hCseJ_1&gHq}j?pF>hs#hzJ+-)8Z z2$hk@EKD_uFv>KFGz$L9F|0({l@yrNilI;;d{RG8Swf00x=`Xd-`4!2nRnfEota3A zs~EhHd@_7ba@>87U`lDKeL!2NTZKc8@vlW$VSslaJ6ekOhoJ|4a*ej57-2VSOK=6L2o<`Mg7vxOE?3yju* z)=`Tdb85!rl!_T;Q#4OOP3=Y5w>iP$ttx>U5&AYHKKE{x3KjYE*O-*oiUJT(rUtL*fyWmzYMMqWm@YR`;LZL?HM;smu?-l z)Kq)}x%FQ7@@4AQZ((=Jcg{R~c_0H*fmz@M!ac&fzdWwGxY2Zm_%5NJ9q+bH-^A>} zQ+rc4EXb6Bax|xDr%|ShkdSe%aTdSfI?Xxmwh=ldGMX^P=zBO6EUIp#?rEL9O#N7i zJ#rhe9okwISbJ*&)1!VzO_YirO({!0!khxukCAucx5DIGsHKPD9&i~Swj zU2kq2*(&BL{#b@IKQ%9iQW{3p z)9bPcPP% znnFLk-_P8)n*FL-WF&p zy>>k*IT>q80J4kPUR26gs_!wdXu3Ka5p5S$+U@=}t^LJj*ygLv#gbzzUt3gQx72*-6antVD}w2fcxgwNynW=hLNV;+HCEPZq32eZQ*Te zr}}x1nxVF%0jg!ZNqjSFd1JS1ot4y5*=g}qXA`=*CJf$ku-WG8TFm zUXU1zu*-k!QPH|a#CF(lbGLeM=B)6vqu1PD-(UPf`X+HkDpaaubRgb!iEnX*tDGx^ zb09Wpq+!o-)c$SfpO`mh)rlfoQTr1NH!07}-_a+Mt6XIMHP0yD$`k6hyocYjT2WdB z%bUxQzl%97z2&??CjkpNMAJmygt9(-?qeP@GMgsMzt1F2f6dO~$#eI+6&(q^iH^hx z=T&zR>}cMf&#A4i-JT3gtJAw_cdFRv2EVGNRhifF=<@mI_;z1}y~QpQcjo@mKligd zX}PlPK5=Mz`?XgS*8}T&^r8!9+J5NpJ&60rA%KpCd%0xn@&3@;6gctSew%}@K-CkP z^5TB8cr!lVE2KtLpv$cks(D*_S~08WzCX`jF_>wJFpm#w6Jz>FeXdg&gq^9Fp_goy zJe8`Enu&Ri+45?*AHJT9Nn6+p?WKMH{g^semFMRU?lw0XAG)@lJIfG~-7fFpd0Bq2 zJu*4oUvRg)DlLoJuIdqb2z)C58{dy6o}4Q@DWvAb3c33ryxw|H*_s`K9z*WH|6kJm zABX+lgq2A~Oia|!+0+F1A6NYe{J*Iy%YQuje~9t_A1D1E*2==n!~99N|KAj~x_hJ2 z?7Lfzw@{vup1s4ezo4)Z4jsZCQXmSfUqGnI=k_FU1svR4*FtO@lR>X+akR}^x@?1o zPF^S_Bo+Vk@pk9$^A0{bx%1=meZRPSJNAF$_;7u3`}@I|20qfabbLJn34R=X{CoZY z*y+AM_WHak+@H0_z$KTJ`(4hfBGK}&vmb=ew z=p{}VecL#5bt=kBvyWeSdvkmvy7%<$>JiW{qpx{r@Cy;J2NBPJ*XC+LcF68;ZSTb3 zF2TI!PLnR%-$DZ&LA`#m>qmPVC(nYtg1=n_K%mPb`UU)uIy*pBdyfX|>iu3-@8GhA zU*MGxD4y+)9muZk4thX9A5^;n2DcakXC4vy#~O*_%kua^`d3Iu-Tr*KBv&&a*4W9r z_tK@ejf?xP2>zKPE^Ub0j_z%#{hN!IHl1~*8r-w~-LCj5V#%nMPzOi z8j&Ydo=(T50hIZKt~t5UfU&#BJWsax-kp;TuHLCb^Ud?*I@@#aeAG@qgjMdL!^9A3JzA$*PS%Y6YJ$I0$WlTBS!%{yC8M?Q`MxU9(>pp}lkR$f~hNzItYVek!< zS`q`)$Vi_q`LsBggs&iaFLrc5PVN!4xLhlHkW-m9_Y+i}9`5n;g`1zyTI$jB2H4B9 zukIYtIt|&Nm3{!iNz75WlKKU{8^M8RD}1L89=c@10Z|1FJb*LhgEcj6EHr?fRxz;6 z)ZBkEATDh0&W$pO^am96F~FRaq)%GBwD&Mlm^keTX{L zyEyg~p>3o~+(7t;igZnq7U$Q)O`q_X!5QZ&0iA0j;hck(Na$^i5y={ETg2iO(IExgodYb;+Qr*n3Do z+x5j_{S2sM@V6cdfPI9y65`o^Uwsi^l1m7d)!pFRdv5f4au$KlS@se;i(WK#SzTCw zcQctNg3jl{KR6*j9+ej&h6~5@X2Uc|_yp?|(e_a`c+VbN7h-JR4TBu>*4VN8408pt*Jsg`*S4hYh9lG|YsSvDAUR`*ENRdF=-@GIM7-U~h0TNj? zbTCWmBf{`+Lmg7q4N>qEFsp)kw8D~T!F2)9p{5wO3!{p#y9zMS1l>S^&p z&-$+|XKrWeVmiVGNaB}kG&JkY^c;Gkms^Q}P?GE-3_K!``O({qIFoCMGI>da-X7jn zThL}k1A{^e=TT6{?svR{Gh{;HvUc*EWPAn6rwSUGaxCBfqV{3}-mZ@G#c3zq@gTq7 z>t#G3by*EPoIUUyI5MJO2YnTi+#c<`@#fmk{E<&m71ZN|-&sRtbXxs(3#co|nWlki zz4GeiUH(cZzFSb?dZ-ep*gFNeV#F4_b!9=V8F&cESbhcV5H?6C|4%?L2*T?dir&He7km=8NiS`s$oqq54u^Z{1mh87zEaLCEl?yJ11U z*%>|%8z*+j0lYfzcsPq?YbKrj5)KzVsi95JaZEm6cG0V2vT}A-Ie47CO8S`_PVB(# z%Q2c+sFu{8EY1CkSWS$w$0!b}sBvujD(n*d_zX@uuZ~*>2y2fifgF%?J+X26+9l=} zF4V1W!Bm1q4GlApy~>6r$o74NCY-0XO)5lz0WAo~%8c;=&(qQs#jVV{mpO z|L!nsGA~Phd7JQeQowlS>0_HHfT087hz+w-ujOFj*1kVCfn)E4HvrfPFh>6o0x)w= z;5v_=hVu=PF0 z^6^9{SZxM#{7(2=RMNm7Ek$qb^j3VL7w9qD(F%|J2X&W&oDV&UHkIHvpSL%N#K>U) z>=~;$@O4fHmoidfg3HF4W`^2?obHBsJIpe}%&%<8y`E{W|KRCevhS#yD03$ylZ>e7 zq1s}Pd9&X6xX-!~&VeH_j2&{2C!p(kmyIA_f_NE^ilOS#=6OR(IBY1kT~G9QaMX}C^;$R`74j5sKvoWEUdvYM+fm;cAM(^Fm2K95gw5{P ze-XCb@A>f%HT#zqjdk~LKtA~zYjC3Dtau%9nd_kCYC-dqQa6xxQ+HC=D{Lv33 zo^$IuIF|~wul|b@bE89!+I7TcM!obz1Fwq4%X6L*X%a-{#_2^N1@(6ORG~3u+E-uC z(LlFM1BS2$7b#Z6dRhT@RV?m@SI|UWI~Oh;bCIw6r(Mo79}hX9xq>7KNO}+K;o# z4Ci8(5*!g@>j#87KpZ%IHe+wYmc0m8A&K*#!~VUp$O+9er+yc`t6EGvN;7FDw~9#U zSA}Cy`nP)r9KJ#dIF|CHs1O;1d|O!TmqF{)KSs@L_mZ0Hm{)^Kq>*O4*@LN53dR58 z_IR8#bwR3g;nSyuN{PD5l==XXM@z*$Y5ZagYeUu$Yr1ztcQf9 z)b{`hg=OjB848Fj)feX;=HHZ z0SM8vCqZ*3c3i^em`FT)_su9YCP1JdHV#s2^lM?ViBgP55E>*d#DKvug07X3ynCzc z%*(|?s76|{VNGre24QI|%d&p0@l7|FCHEi7%*2i%q4ll3&x|7OH+GE#Ng^`GYRh1H z9@~+I9#ZjjDEt$3e}g*~-&5SgrzN2KPX~!pb$c`y4L=Ut=f!!+-VU(>UD2ACrDjX5 z1;0Iw;9X-D`UdXhsk>N8MvPJXeal}wwl0MQe5)CX7NgS%2Zlj?YL3;A%=OU{dm7L= z({KpQ6~?}7+GUe~$E5~AvdM<+hAqdf3Pm}_b>^)g;;rn~9-*OA@yRkSXvq~{hiZ2l zk}Jfr?&R7RNH>S-)*kp#uTi(p>ppvN2&iB-^K0+da6`{7DK!WJiD~pD%qY4JP&L$G zRv>3xawca0VvV^84wzm{rd@8>%$J4@i+jdIi^E&e+ZZDwX&XVOEa@&CcOpC3 zj;%sx`9t{E><0z2VME@c7TDZk!@_DAsR!%yuiSZRaF6Fi8y8au@;##wU_uml_zUg} zyW@fwsq}v86HiOM42c9AfT>qR5DM{3=#o&R0B%$UYX!TJ*ix*;InF5a{OcC`5JGD3 za6-P#xc7@A1KHXS`nq~GRQY|lGLzzB#f$KJPm7~tBau_n0G*=Q%ML9u0i*nFPZ#_OLD$mYz85Y<>)1y>Wn&wWIhyVcOZ&EY%*3t z=V+v|%x-@yF%%MEU;<^hWeq7bqR7{Q5lZ~u>b;$e=KD-^G=5_f^Lo-b3(NkYOtPh1 zD!{^0+sx!2B_|&zsND1LkjG`ZaL2Vnr}gIybIW?UO8OA#mqOf#uKPUX&jRxLYWOO(&*sacX;7Op9IUIBhVD^hk&bJ|3FbNOJAmawH7>uN6 za5>7NnOT|9XYA}l+MOE;s7mPe67R1ssJIPuJwf(!$CdHc;3C=ZFLjsQhH}{{V96d} zAk`IbL} zui`_uq(N_D64L{gV!Sx!rc&qd#(2~uTD#N(Wg^mZ$p!4P6t}Q9!nMVFf3snO_4^Gknj~pHc?5%1B`w*$u zL}GR6#~E{5uusb{;HmyA5$B+_nAAu*$MYp+@=GK6Qx*VHUjG#%9tY--bJRUsC6}M_ z{=KbOm$OgZ1S5IH2K5KC)T14YJo)~f5Z&%8d~SIrWCkUt8HHQQT}9lFa`tTDO5&Hb zO%o*#avrhFy>HwK+%%$)K_itLXq;y+fW$RnY}6v`cFJg)-b}a@MhY@ACq8{tw|&>c zd~^Ii9s4Hr*DnKZJkMNa;h;Zf zm&X;GjdFQ#{{C0S(H}xgWjcr4jVg!|6B; z4cHvP^c^<3*yk{dd6#vGj?}_bv#^7(rCqw)SwmiEucOq2*j%-QBXgzle630`s`iUyPK6b}v9;hYrqtPRE+(XBNn!4YO$(+5Q2#E^XZ351Oob zpm{cf8Y#qBP13X^7j|^n5z4;Py>(c`3yv@Q?(m?IC82zmAX}13Itd_^p-9{5aq+G| z!)SHKn&K>gO}Y1oh7zDMPo}=)!}WxCUrSWFj@VF-iP+0=NoyV?&%kpiA74ft@i-R< zXLGvvj4n`4@e?&&>x<-t4nLseDi98tQd5sYcAUPgi6T*P!XNCj8>(pECdObC&Y#^6 z0!W&SY#Jbtu4L-eL<%sU>Xh#|Vb4lJRQ}WmH}o3j?yBE5e6hvh-z)H!9#n1*L~- zzJ|FkBj5%=pzUM^I!W?E_WE|M&70qH8?>FNd^0#hL+p)L>T#GyHHyuBd#VkU%?|0z z+2`z^V*ZI|%^~GWiyIxqCAp5?69ibhLRv=QH&{1!v5}ln@{mu6#AFT6esF?|N}P>{ zAg8Q*&0Rx_@76&n$f$-Dpv8Y*T|rKYf_V|aygmv&lwxYCh|3vMiU|&h2~DCZVx)FBwc%XS>^^|u#E3q4 z+(GI2`>V0?yL`6>$uIJnV=DTf9p-*zI_>AL5WY1(R@b}?rPSQDQde}P;gVN+aR?8_ zF_ifK-g?78IR3;c?*^o)&}a}mduIH+b8$_~KJl7H5-vY{XrkbsD%bhLq60S&SZByM z6?$BcmraZoP8WR3P7R}p8JJDVhWilbcld$)dxz8NApZ}X&C~-dVx3IgNf_U7?J5d~ zy&$|=a1qm+ASI88fz+SuCL4K-hrvuKc#p#>yWzlAPOh_1gbtjsgf|MrYP6UsgsKg| z5%y2k&cw7txB?PQuUB(Cla_&CaS^vNTDHI59z-6qvqQe}#6;6!DQc&@w102_w?wY- z=%tBJkmy8sIo5|rfxw=AkTGf>nvra^bfptBY0-(5wrwlM?i zX*vxRcPuO9&#?^R&d*edC2k)0upWWNjEcnxyyA4&Ofr?4)c&zvw8`Lv^)r|x>QQLI zmtIN!&&r5G6pFe6thB3!l5Z20EAoR>OXi>*mg`58UcP+o_K5MgQ`C1f4nE;~r zyW89Pg^mrgH|z@PqX}G*zB`=rm!)>Tk#TL`ubXfj1(mijJR89)^RvMhr zT~LYch?ADpSAB~NH3epaetU)y38#)OHa)~)p&W$UVhm`F`QxR52LV5M3ef^&sobE{ zO3<-(EmGIX(+l1)r&%p69+8lHoE*x*Mr;y0QXbeaEnuT(`j=tIYf?n+L$`A$l$1!fl3Sr!R9eqczDL7U!=&= zN8)08#=R)52QbY{PztM^s3XUP)ztb)45_R&Nq^Y?&ej-Tx=0p4 zXl4dk@0Hlmn@--=FTvZC@?HFS(0MxVM@7#RZ9))SDlUB9zg@U_z+NUfuPUKh<~IpU zs$7jqZtV!xQV@+fxPa%%j0%U!Tl@!4!V-TYmbPrq@J!@+3?1<(ytmVDqxnRQ#2tV z<6b!tQRZn`_AHoMm?q#UMd_LJ6k7@3EcJm2#E_9}4fjFU(gbH_P=)d!2NEA&1$keQ}wfy?Xu^HxC6%~>Q`)Dyd`NsTtv>?W^q)bXQ~qP zL`Y_gx}1wxdb$*W9RzV^%dg6m11wRMB+2NPKxfD4101x=a!u<2$;Da`{O5}Rpjlfr zhP6Fx>1{w={3^8Dv<9IsZJ2SDjj=#G#JV7i%g>52STXx1K460Ovay?<2xdW$$mKNy zZb?7Z)E^HFnnTrjpm>Tz`8>{zPv4B=u{CCAe^oIG?nFA?WP!L>W^n`b-fVwdx`Q!f z;wCei3=1?wOD1U$@{d`f+=I#hqpDf)vc&jkDyV*~$+J@`@eSJit<(4Zv$t%|FW%L|^s(X~l7G5J?L4|6Ueg>D8@v`(3ZC7O zNI3AI3BL!AmD3SW>a;@0;x&smltoz|G*&g7EFdO$l2Nr!kMF7>Do~}v9LQ&3O7Ft1g(Qt+gh$j&Kkov(6B5LIFQ5gq z2mbcdB-)W z%k{1db=WZF?xsAtK!^c!)DVa@2%ASjC*NaJSbW1a5>BwE9}~|2oCyG_YuOK+39w(n z3{e#k!=H&^uHF;lcFPe$P%2jc7Dy@uij--&M7^3?o7Ec7e3WyU$I~O!s0fCs6A+Ve~Q4Pk_H>&ba4xJk_oY|kb_mq57Bd1y3Hw`Ra<=(+t#}gkN)?B ztm=rY$!9wYnk*zf3t1v!@g|p#+cpKCVs`N)8nVwB_DKU!213(c@z8W*m2}HvCstz* zVR4SNBb`Ul_ulj%XvhHgC`+{M>6uT#v8!9A52#P3Q7D!G@=AV(tHJ$kBn416YEYrw zx>4>miWyyu4y<`qgnaSGK=^(EEzXW9{kgjJIU!QbJ@Eze^bh;?5ZdaBOr!$klKlLpnc$#D_Gg{lQ4LtaT%SM0PH5(iVhbJED*! zQX&cL@Phg3Ola~9Fj(AdR`rEB7A>zrRxThs2~SVyed*>!U=#<8M2xe0l1Hpvv^4nZ~_24^JLSi z*pqoCya4zb!2tdG8}ztzLD7-FPs+9(*)Zz~`>u$c$q*yYfmpioPym*+no|m94Dqp8 zjf4Y=0Cb;b5V`z-q=FQ(cOf#vzx7v0(I1>aRs^^|Qs>k>U{Ww)D&m28OrQ4KSvK$@ z#F)U*efs=}4j1&pgjr<6XlGFW8k&>|m}|RfHMT-*@OW!xtKm?b@0gUzM;SCJbVc8v z!J2DGZvz-A&JS`{M)!?rp|~%tt0dwe@lWZokaPTO2vSuWDmK(56MC+rjh_I}}U z$LZGv0!+#eq}#mPchdtGPk^S4UVvS7?tz!FDH+uMXw(WxhxHpYn&m^2iYi@VDBL)E zE(O%UAHx1rlh9Hn3HL~i{2|>-lAGiq;R%Tz52CE3qNk!#18lCgh{ggi%wzjQDEs$l z>Fw?TBB!Lx7}4L+zeW%T@e<)D_|=1M(S!lgmE@k0|2)8Ptns_w4f%q2C6M*5;l1@T6TzvinE#rMzJS-1)4muj* z2Ay5M`TONJz2L2}Sph0Bbp@ypRtnnsY?w!&6{ZOf!tQcROEwIe2Z}r)bzs9Br5*8{=8gf?5G8|DE5eVIgH_4%qhaGXL1h5YaCSSL5g13{%J zxpd9}M-2J|D`bjZ%$M9|`~vQfrECL6rr6rGnvIKlqtdlDvu|Y@#Q$4*U*5#DyuyU~ z?Z6Bd;x7WE&R)~^Y?E>knrKJ*O;xr?NI(?r?rsW+^(j5UR_zx)Lu+of zf72pHg1C-)3IoUg1#v)*zeHwbBRE_;ktM(=T`b=;OASI@Li;sK?G{q6osojrkI&>q z5xytJ7D7rb8-RW!H|sE6P8)`tEHzl1yymEB13P6~{D3C_UcJ*d5BE=s8jMpW##tF^ z0R9Oix@@`)0`(?CJ*_ZBlUCI@Zaq=X$xvtT1a!?%cM44qqqQeP&AiFvBa|47W~gT+ zx+FSIE|zPCOlka)Qi!YhPz|gZYA8fR8^8oLBtv*p|D-AQIJX;u1?o^uW3wSG{B)oM z03ejjvymW4P=_bH#$G4M{IpC$bytuCB4H$K1qdtC81OaOk~b5AH9-x6=j>XcQ4-XH z&}Jyvezpo0ip%1j_9n@-C#bpChMLG=D`#COJy&|#TP2>A`-LB2lb)Vt8LjDQ5U&Qg zqGqR=183i^XZ#=M%Dl)dfI&YaS659o+E#3=m%IQ-MLUqmho+|?;1r0UG>kVRD@50{ zT7HWgm{(8+i9{(7sjApoF(__A`ZYfd+pz%6G5<9AX|UWc%cSs`U=O)1kpz63{4|L9 z3s<+&)7%Ms4B!FGXdYt zLiMclw9`yV3rSCUIx879ZF)LFMFMry^rWYencPTGQ8zt}d~mg#joO08-x-fFDlJ0a z8)8$eLcfrw-1-6#o~Z3KDJB=FL8quu?adeGdMg-b-?k!LK$E8xNhWm=CMDN~sfwkH zULB7~v7QsA0;5_;4DKM?8-%k}*T^zd2Hp%I|1Pmqfz9Q6t;N?|HVnbpvG{7<6=_c3 z>z`(^Ti&<#7)%#$8wEC3sT6Vo@XHQi(=tn-Dj`>5?%YPipLnOPcb#lwvyx|nPRz+x zSbpC+k(vo)bkOzvr7i^xFqt7L?wC}(dn=J$|2 zOCq93tYOIozNVqoD6ZEleIOAc>UsLTV)UE37S-T}Ax3&q9X%)A`pg=v%z-dTDe^K` zrvQO#FOgb5$YOeeJJoeva#DEwQ)=*NC1EV$m8oIw&@RVgn~{^I4;w!EmDL4Df`RQ#U&vcLWcBlFiH=pNXgdrsf|t*3Jq6Q;2Y5Og3ijDLBH8 zb9*NV9B+N*vH}?82#)R%c}kZo0OH_oAtifDhZa_{yA_gs>!}&?bZ8qL&B$?zq>&3L zp2#+95~O=)%2=2xT(2Nq4G1RGIjl7+N1l(AMWsv`%TReykCDcj?=)Tr{UQ>Rx~P3e zaJ(NlBgxtz$e0F$?~(WSdk7^CvKX_nWK4u&!6GRu6QSz0>zm@UC?WEp^g|n2q83Gw zq6#d$r}9SxHXhbiozQBh`>2@q#JR9|vZE(Wr%Mtg%N(eFgcsOn?oG|y$xh3o%c*rW zJ}426v6QcCwpa$>iK1{6QWXTJp2g9z}iUBOW^@;(8+=(HGDU1QZL>Dpa&=UZg`sxWg zs0OEc+QE%Vr5}iXp8$_)L}v|izmylD zpU)mjG)Su|UMd`7zaBX#8N_}@0P#PIu>rWWyk66hIN7I-u3Y$>0X+GzA1*9MUzsNb zh3FSAfTjY5nZq;$Bp`U$G(j9KN3hL~i~R~h&6fk=Qdy^J8!i!5V956)>lgW-R%NjtM5?L=(2O<|Z;e>JD!_gS}cfP`?AnSqD_JzNCh z1u=Uu?Z&SN8<%#W8!XjS0{=l}YP!KU%MXJ+?T1V|=0V6z4`N*w5{6AuSef2LQMb!Y z0Ljf)wuw?DbrdGnP}b!_i(14_}kB9&Cs&=;&1ub5fmWNMJ~k6eyVygEGocA1!{nhQU{ z5PkPh|1;w4agLceWh)K^Q{|YdOT+_BIk?OAF&A6qs=rJ{UX3j75g`pzZaB0G>7Dw9 zp`(1AR)i}NH8sq%W91FWa6`;HZ6LR8)LMG;tS?yyRep}s03fG~rdeq8&VF(KPrs*` zGP>%s5cDiyxvI_aKxZ0$Ll8VC_ zOJ`AbYcdgNGkZ8v7SqxQ9bl$gfZ)3vNr7UNhgdKDz{!{oIgQWpT##&3OfD5`Dn*Nh zmMF$m2PcLHfFIJS*L2s-{%|pbAGSf^^qkZ+*Drt@5~V>GzFh$(InPu&Z!V}!X9B|~d!kv01irL3DAzQ8`HEtG^ka4G*D>n3?Y=9$@ zgg;y!fxXlehJ)mSLOFdLfRu(U@?xR}@oOVy8&IGWJe zP#NysIsYqG3BiK#HlVw(#0Y9yr38Sa^?Z~GnwyC{TDhCmz^?O2_#O&V{6j2`-gWK~ z4l9K6G?0=IDu5u^-34?6%LmE#$iKy}T3GkkEEouZAX*nb z;2FQp$;W+htKcXSU7b2VQ2}dk50G53{Hi~q+5+M9U1ouyW+S+p&JnGA4)u)gy(x~w z*4fJ=fd`~K?KJmR!~0Np$FLKW6J)Fz9Sx)>vQ0tZv#iyj) z!5lGL1H*p$lz_Yd>*rbq*s0qf>7rOL3suyMz=kOz6Vh-C!6F~WL&H;Bj?#{1)|)`) zP~0JJ(?r2A$w}?738s$1d8~oz@94w}$3ik0`jTg=7nD|braZJ=Mp!*@o_#AU%(1Y} ziLUEP0(X)zcsZTOG}*SA7$>y;fSXkkP8;prYNFp^ABPX^qwY)Dv4la^t#X?cr!mf7 zKF%=}ipu)>&}jaIZ?Nt#q!ZXN6tfl1A{;`h40P-vsR#vZ@}MWqxh)}vgD=O8!z|K@ zD-@>pTu2n)BS!h+-uHf;SPEV6*mEsMpjQ(~OPPW~jhe&q^Mm{rjNdg-sU2D|HwDFX z7GVyhUC`Zl9iX>zgorMc6EKf~dp0% zvL|>2;iScWP$dM=r|R0!L%v*Z8@!iNjwAX~AjucWtpg@Wc==k-RtTp@QflF8?Owvu z2)>Ak)9mg#SDo|G3p{|CF2Qp?DgwW@ABT_j!wSXKWlFdOehn{3U)D)-4COYFwd=V) zl1nYcsiC1im|NF55mW71B~ z3F-BdR;i(pfbg1K4Uu8*Ng;%!0$@4{7gv^ybVIj+)Ip07A!Bv{2iX^bw)jXw9mNOh z_km{jsBZ$YPtPGuNO+u!^85VX|Dx_Ert=PF!B9V9KVv&sQiN_OQ}G1P{M!^gf)DWI|aZ zXsc8jxH%gTL2?HZ)7V7BZtz~-yc&!h&5aPSW* zB`>anq}V_NsWlXXcr7*%BhYs#G}o&Zft+lhsR+*~4hWLdYJ-I#lygQ7(f6JlAz5u8 z!Y)A;Jd0%BMX;hprrJP^18;_c`0DI^F(5|XS#gX95`cD$R+K8%o5iwwAx=7Z?>vZ^ zKq%+)42c=Hd|@`h-vY2-@`Y{`M%g*(T7>mbR4E+5GsTAf#z;tQYxjv$WfvxUu=!4k zkvO~xuc zKoNomwZ7~RsrKj}sNzj4vD6zVAY4ZgSzeh6NQ1H=KTOSiT-UQfduomnlJ%2@{<5Ja zj?^5klAh(kx?z*Rc(8rPKBzo_nXT)HP2vv5*pvK@;z$kRJG zb1voUpNAlAdVC4ywo#(LELGSi@CE8DH_b#pUQFmoX-XAb?-c24Ra2@Es*r>YvnLt| z<$5Ox8>%%HfiGI9JjDuPxa9UTZX@{3PW7(c58DGWgv=jN}~fTUVsU$de_ zn4C2)%E($&D}?f;-X>iejTa)*AQN%q7AC^6q-M&IIF?i1h!&t8$W$wc5s0y`&dTFw z5xmVJsa8Z4AT$B_8QelFaM-D?j zLzfScJ~jlESLhZjjA_DLKk)r-C$m1+gW}S z4m)Cnz{0Q`az+tD7yc$%tioEM3khkwnihvObTLI;ZM#Ajo;=M%Rw3T7AJ~-8uh0d= zGYkyV3S9<4nD)@+h|NY7D{h&U1WDXt zBE3~2Fb&<*Xp#iqlt$>2#4V7GbdZ{2MI<1c+4;3W=*Je|AgdROv6pO9Fa9!8;uiK& z{b!TDcIi{G2L1x|$R9UYyQxCL5aQ0U`FS;%eOD4D>gSaf|a@cP3y0mgiF9{opit zNR|cXcn{@0?|6vu>Y6T@P7`5z6o!zWj|8y22Eqz=HTEyLm? ziY+-~9h^OUkYgs~O(3KZK|!)%{C*(y6DmFDMqDa_fH;zeo!VyJsKRGg_p>xS4@Q#6 zNqw80(bECJ?{yrkq3?Jc5EMk;@%W0NJ!JO1Co0a&qDZSr&>6bG0_1{JC!Q@fRS8CQ zgQ@~0O%5QF;ybDV^q>djE`?mI+`-P10-6Q$bgLW+6%8FF3zhZl=N(-}GQpVdX*Xyi zp%^q5wvgw!q)S>0GDIfeHR&OOEtcD{O5|x63wyzNWRQ8ugFK99!`8BSo!3_^clE(~MOPN>i4C7j&PXJ`BjQvs4#W{4_|#L`hlDv( z$ZZ9OdXLIj>6g<;>4$do9S>)PJW4&2#qf1kNaEalqOq3b(~1suF(jA%_F6%7FDQpgOF0d3S9DmQkOl*FWrI7 z9bGZ3U)59|@o+ntf-^@mxUbn@Twio25sZjU!<4-iv{Ny{)hOT-U|H7rZJ zO(P((Wl^wON1f35AeIf;Mp@^QuAfV-A;8?x72-OT<`2U1jt8m?-IQN01j*^9i_60W z0DLTrspF7y7i>i|BH2w3y<$APqZ{)@ReQYZxuYvNd22`#(5?P9xhxm|3HX&M* zHUz~>BViBfSLY!QTk$zZ!msuLF%fRVX;^ndB-ZDkX^zAUTK*Ilp)V^%8vO8i#H7Cnzr=ys->=@6BRSHwka~00|_Tpfc;@ zFb(_vmT%?c*s1YTM=sU__s|rtE(ekzBsW_F7cR3O9#F538b+E1XX`K={s$U`0?!~U z%77Gu=P{}Mpd8uuY(*}q{uM0#KLTevFRl)jR{+GMK`{om9bb4Q3&4sjxfkKI&ebO> zNE3^Y^GSpxwXl5K{IjH5hh51{9q8F$5GfVITa{|X_z$xYYDN$ggj;zfOIxj(0xh&S)vKg~S z`4yE=Cif1p1{|E&59(22!bpwwf>1Qg01J^;Y(!p&8!S9a4LSShNXh)LpQ-=U-uwKL z-7vNW@lKPoLKzGfrQQpBts@kkV$);W^W0PW@i~{rcddt)r?8-A0>8&vvxiK1?Y!;e zZpQQcd%Sxzb7#S?idS-|$=2TJRbxT?rV<~L8gk^m7!)C8&q#zEEqZW0h6f=2YKGm| z07f*hYHCKeN0qs(dw90=tPc>L#qV8DfaL(n-J-tNB^&H9q@2K8(=HgPmI1U#)*Z#Y zCSt)F!X4NXe?C3?K-P^m$a$aQmFN(_ekNh&d=$^i2m{fum?=&&CXJCc1fJCtw%@dx zjG(c?*3Q(n$6Faa3Pr2PCDzO#rKw2L+{4RE`znPApg5o?dBifbZ))2$_gEZ_j7blz zpiHxdMrlgtA|A{X^&1j@QL{_^Lax38@ZfhH^)9#|7=16NY=06bOwrl_o1!%lN+f8l z>zU{P?Lv+$494Dt(P_j@7#Rbfs*kzkts&`)I9RkMp~)cgzlfu`IG6{BM=^Trk{U6; zLW*#v{vpX2j3>GT5~FhTO~VHYa(2;UQ(RmaF}LQ zaixyV&uti%1IJ6+Z5?B=(S{BVfASx&xpiwIJRCR(UvV?RP*;PM-_z!D5k zSIXgLa!Q)9_4ZSMJ(#WdsSg*7`(y^f=Hw)bkm!r$1tZ2QMrnkcrQqre(vSB%`I1IeR`Zp@k$=b6|hg=x(k1bgI2Ykdo8KfE%gw=l_) zS2kuKx}h!ol}#(Ox6h&5^W(Gor0|`R-vF739}t%p{wc=nHX^K%K!#BL9v_Aq7CC29 zK+-&G>AWF^dzJNm-Mvv+R7_$%4BLF!-g|DsO5zIob4u8e?OP<}^ zq3FEpEy7z2swJd6iT+H%wV)d2BsLT6Tb?=>-ulU6yy?yM=1g~AvK!um-_j*{9*w9; zA!$dvM8BmtBybb#yLz!qHOIpPQ8PiKDgDTawFsB7smWq@wrm7P|PS?Hw^0s5AgB)kgQ0GA4DkkV}>x{lt_di zJ*j~)Eu;w1a`uHJrK(xV@_*?z6&ay_Pmio6bl=?~e+YFG3=t#+MLUwB010wk!8uGY zp!~3z$(k=r#9uT>tK=QnH>xcf4$rBh6D4G2GZBuQO}aq#+ir6@Cc+g|<<7tn(dtH# zL3Ls}aG5(KPtF$sB3IcBA7mFG#@aCiW$lF7P|aPAhx{V<@8t}dd+R)NPzFUj?VUOB zgLtwX3>ijtH5`XC(wrN+K3r@2xdHWNB@8Wsc?NwT-p-pP@y@NTnUK1VAa5KD{cCZJ zH1w+xMNZh~dp$jmz~T;cR=x-z!jXfm@F#HOgK+<%EB3TL>qFNP5z-2BcxzI1!1IP0 z5-8=l|3YXpzN_#p;=6cS9L3t^^g+lwL>0%PrfVD7)p4Ob=H418gem zSrL0AtB-lol>o~Hxear8(hRm*W(|axtcbQxmjI0HGDO%cBuLH_MCwXx2-bJm+9Re# z#uBZOHmeQN?}04a4%S(19>6wp5M2pITI(A`76Ky>a*vk70jYA15*?b0#y)p)n94U{RA#)klfg~}om%asO@D}n$5^$evTBpAW58$MZg z5q@Gyi6!>ImuDnmro?V&5z5db!(il!p&J-?)sR@w}T)J4o0o~#!xs8dG2IW zNW2R}EUkZ{ILLaw6tjN#!n~HV5$-BC@xY-U%N&G(2nesa*1BUgm)2;q zr5hF3pVEwEflklXgw<09jdb{4N{pB*jW+^9PDRS%VH)gohQx`k9%V{un59C{X6lz; z!s5wnN$cHt@nx=D^jetI&ZF;p_Ulw{Sf(pLHKeo z2S^ZvR;1F4xR;FO&L!1^ZOFk@2t$cbc92XDW73FRsxk!+S)eRA$ww7=TNcC~TJ>#J zty6_-ZN)~Uniiz_sMeu>??tmB;lWJ~2Fn&jIAOwMu-wNySntpt4Z4r+_MBGWZMu(&a=c`mV}v>g zzIbM&;;5i-h%UtL=k+z1jR%+DeEkn!&Dxn1gkk{CuZR8+sqr7aE{#D1pbqAs>paA`7j z^h2oZ2g`6i%}CJRi}LnEm-yO)7)LBhxu=mz3J6C=&cO7MSrKQ>u|fKQp;fXz#MlyR zIgFYI+u@7s7^9-$SKqedl)K;L#7s&Mk@t|rRl9K@m|Qs#SI-}rajsS>CHTVUC#e#o z?}?-AIOlS%tcPkZ=7m8Yivl(Q8M(0}TA`MdVDwG7Zmc&*!YC-sQ$ffin~k~fr&>el zW={tb!5wqm=(HS>N$DnBE$1eNDWy?I3KCfhPbggv^sTfJn2p{@Aprh07IGF%yse2u zKR}IiLsRn~hEdrA1{!3g2AR$NLbwvoC4`auFiJ%F)bQb{h` zfL+55_C>?)?bsOAKfnyl-lgar#0kJEY%oWRnLwu*#^7*UHgG9nC|D)$i)6tm$bzAi z9VZT1dYB~Jd8!n`ne|xgx|4Sy;}csxosC3P=MFI+LMSBQ!bBisg`6xQU!a_sv{OYK z!x|N0i8H1={rda1mO-yf9Tge;6G+6Gp?&p6M`eT1y1qjNf7-8?BE^hrLHG~H2BDTK z2m)SVqf{o@bXs8~_(xm$5Q6=Bejt(~0X; zM!bbujE(1h0@cbe`j4>boAI=-$r~KT+pv9h+jxiRZ5VnC7Vsupe$qbQg>0Bv=tnDF z(2~*N?v$%gnhu*VHxM*>SQza2NVzg5oZ`>?>O4pH_FIQqY{^$vT`Y_)$*(ZiU$$m4 z34Z>RuKg4FM2Jlk?N}t8@*bIqHMio3$SyGoC6t`U@_n=jK4lpko6Kn&V_^{^akS!m zp{=;PBEvz*R^6zD6j5I73aZtzlGAzK)^4K)*d z3PNaonX=vkpjw-srB@8nDX4tw~CxF6NGq)G3mD47H+WvzquXgC@gS4@cq<5G`C!@Zs9aErzXLgHL&W-{dF zSs@5X#?arz#sWg)WXS;dVQaoJ4tQwBonhBLj=OY3dB&Mtm>WVMi8fIb5M1OP-!C`O z`5dRKaSYj9w$0I*6l;>`;6Hv47KB#1C&yyde0m;U8M)jk+8X}A%?}&gE5t+V@c%{ z2b*n03Y!5y`bs!$=CXzum8^~qsiXoWU;MsH0q>cAh~Q*1qC)SJUeFw@HPMvchdAOC>0BQ!OI%VjJySf zQMBSbC%-r=8JZzP3n`O+Q+5nVaqd%b2EqkdiOh;9fn_A!+|-r8I@s6rlSPeOX_b5C zZccYVB!wOz)L~2;kg&f8Q8i_4L`79M0zIK@LZJ04taDdG3}xbs_z$m{Lwf8{TvAns z#tVcPN1MfXE{L^(QMqP6BFJ1>27#LFpQl$z<_;1U#@hqaZ(7YI1I2yoiA;OVyrV}X z=C(%X;7inKnz^naMy;IE2BXAj*j57)QF6%!BGD4PXHEo$vg1arSng|x@LEP1)oFyf zR9PPocauGc!KT}^Lh$;-)(nK2&u-=&4u|f24Vi!|z!1tQCFur2bOl{OaMM{X2r5Zk z#^RW-zOBsZ3qw(@7D@<#WOd69Q$>V|$VP-Z`$y zq!@`j&WB9JRyZZTXPt%2P8`i5g2g)$A^0hS?KBo-f+CBU z424%Ux>WfPtvok1R0rgptGjbkL)oB#$k+-gtM%BC-#)Y|@IiXSt#)#UF*c$Lhw^OogD{A4N_ z2N%fl3lY3Qw|34FeB?J;;RBEnB1z+n4GC>Ao;nbY`OAS`>fCm`5nQ1<_Bb3U$M|C zCr5s-Bkh#x_d=;THQ8rHtDKswnKRO1XTI)V-D^$~2<**)4ZEPJYqa$WsrsEib+0)$ zLGc_gbtBR)!V#om$M$=0B0~MEe6Ssypgo{ejKF9fk$Zn@4W(?pScE`SK0qXUaB@T9 z$X!&(b8sTU$*O!;8}LE+j#f*aV)L)@SrCwRi=m(>31ZG^FHY+FQv^5L&(%=<#z^pM z>#DG39-Lru;f&ZNGbOki2V^@q*|7)=w=l4Lc_cM-oIm*xG~?hz+U}TNBDW%f6Cxea zSst7?Dis$Z*a^~HXlpT8*46jm1O#J~OvqBGu6^z$ZE9Bqc8)^w?FQ|iw`VV3{s>bEqnPcwwS%wG#UJDh zP+@v=ObzjebRhU!@>`Pm)+LD57Y0)c1|e6)CNLzD?MO~fLKRM6l^|$O8X^c)plEs!Uiv747p@~AhUzrXw>gGL zqzvYN@bfQ6gxI2Qn;%;3>;mHwq3aR<4ukAFL+)Sr0TwWH4$73IYB7Ab3z1IM&j+2XPp*=SkbId^dZmj)rzD{ z15I@>Dr63o!yR*dtVX0|Sx||Sg(4iR@NJ1tPB9B5L<#Sr*79ovtPSB#j>0U!tb)9U z62;_wcBymD6Q}I0KSuB&eM(o592`nl&LPy$yQXD7@MhIMTPqAjJsw6P0m4Ywaqh~@ z=a{dbLtW%K9i~9c4?lxYT-8?o7y$^`+dQVg^87V|EH4(4p~#Z^&XbKUsRzVXZ1!zeXUd_IeW-0grPvG8yw;I}J(sVI6m3%)4`iRg-WWEC|8gGM3C#cH$wNagL9!2U`afAbQRps(b*zF=q;cyRP2ai!A}unJ8?!(O_3gW(|%ASVHmAO z!pz&4kX(r0)3-<@(N@_3BAgkC$Ru310Jb;kpDpKFxQ)m?cnVQIOr{^{@}~$E()ISI z2o^>^l_xsB?866PpAZVS(pHqcxC8p}xKWt{8Vrqz7vG;E5F;C;(3%5;W?>W(SwZ|M zf*|j{Ly2wZPZ21_Jh{$L$cc|XIE6s;GZi$<3XAozT6lCd!<;oo3+5@mbKtDZ!T1j? zI#Wlt$WN@HKSZF+z)#ADragKCCrZf_$qx}k`g$Y4^C1qJ*&8bHS~|;`Eo&9%odzi7 z?+|=NLeF;FTKPK!G0j^~ERzLl9o+C4X7G0in7Rk8rf_7BCG1=w7g0A;Z83u#1t=>fRT!$Jh-zU0Zz}@ zWf4!FMDP<#WFjj)c@iUU=iI@R3=x!I`{m?Ggk+4u=0DZW55`zfp=VENhvP*%F)h7g zQ6W$SfegYk)-I7YoIObgTH(r>tOe7Mir~172zt^ag40`Z!;Hk&ur@0{L}kCWN(Lr_ zd-U`{#va|4h~4BPO2p3*bo4bG*%^<9B#JJilOnZgN%9vYM^6m38leK0E6l7)nIB@i z*RuC5Ca-Onu2v79Fh`gJGsHi_uy`x2V0o3w|1jDhhqj&oZW3-#bhq29g;6+L)j zrPxm#d&n^dPa<%M`=Kt*!ISgnyucm7@N@8#6+sPmyX+hCf8o=`zUHKqYaA$u#u|KbSppx`!!msm)+;A(%DMX@}muQjY#9fWAC{%PZRzBj41J&OXy2{G%% zwQLpzgfD0~R~BWEWG7aZVQaI`b`yvaN3+!z`8rtdsCL%AqQS6ecdm;xxF1dWLpRlB=W887y&`wwHQ(M^_@^B0(=-Cs{}g=GvV&QO!Se+|@F&3+A>jjx?GG6UaB}!1n2<4*NmJJ^U-kDX#rkIBh zT#;US{|(Z^3={&SX50hlA;sr zdMLsdG2M-BlR0889gB>GXTKtN2<|EBiYo<2WN6%e(`xoYwcVa^@qFNB8=bM(@l1V9 zK}JWkGKDo!xB;KR53a?4@S`#^T9?c~24RS=Q&@zNuPmJj@mhG=&<14j^#J5?P#nbj z2shmt5rO0f>%ySkZdGLPTFbK7$haC{6B-)p-|;L^FXTLAP$us2g9uqo8Jp#~6s8Hr z5v#4F0U1IO2GDrJygmeUKz7dFTP@B$_FQ|jjXwLAu{wvtF4L+r-Fbo4Sc@5dr~Ja2 zae~8na+Q9yl<6A=8}C6qUV}-i;m_~6c$Bv9rsh1DgaeH26Q8NRNH%8wQyTQjzyX6& zNqy6a`^TkcQ{Pm?KrzFcJ@pO7%r(qSee*Hqq(&BL>YExF@_};op85u(;FO`@AFmAX zaIx7KyDioLnRlud1AX4CcanAYR@O;zRGyKV`Uc_o3O~mL@P45s5H5a|UQ^#sBB7kW zs`M4v4QS%q9avDQM(IOu@qhQHtTlP>tqEUl-W!CA^+sNbm3eOl<%h|gO7q^C9)X$} z9x*QrWWr<|Y_;+%tenffUi;e31Qa4qV!zc2vU0xNe$9JJ{Jy>g6Ii&XSa|H8mlaT@ z$$Y!D%TAd2mSg&YF7uwu_r^u8MNDg2&dRKocf2tlv-%wt;w2x*TY6k3lAA|U!>F7y zl0!N=79f}b7G&^~L@39M92_Fk>U(m@Jq6MixV7)i4VVv>MRV#ks$;8c^4K3|hg+n;bt z?%3=Yv{QE*chTzEuzaQbK*&E-bmj+0ZI!L9kvC(`w^6+qOI?Aa{8$Gur+fe%Cglf) z^Q2;J&X0aH4{?=|N~u@sjW9gEK- z=V$qpzJo|}eiTb_yRqw=Um)dqB6wKp!e>qTfe=qt@igfNk{2sf>?`RfmiivBmEg0` zt3Lzq6+^KwuRZBUJ026(aau_~5PoN+iY*Vpx<-QH&UGdIphT=lpjEA0o;8we}wCrjnWniQA*0I6)|ll&2( zX8j~de_^Cz#vEB>$`|Pu!IMau_S9Bx!pJV72|AKlKlX%dPl0Cr7>dIpiuE(|X8pp6 zgEZ^6BP@5aeqS)8&Md(Z^9M-t#lFbAl73DhMb?L;9~h}Lw_FE=+^1{dkVOWw(#oVC zD<;Zso0araw7En|t3B(tV@>;IS}>XJj9$!2`|*WdpcPBnk6BP=={G0s$7*mwJG^PX z=rVJI$zrawA96&dO~zYkKjklVa!lT|UsSHggely?(I@Z65=kIRb(C1Le*51m=XXG) zx%8brE8oYL;EQ*m^F`bYI7U1?Wkur2G6Fsp%q< z?gQg@>)2jfCEW*tTdAmnkjP^s7Sng!i_FwHC~|>=@mtbt#gtE)?_=&LuhjU_#(bZo z2P0wIe$Dq;^OYZtG+s)+?})V2+-_}z`djDR8yN$~Rxw{Ni6mGj%N=KL}f63yjni_O^rtW9!faPi9d zQQ^kPupG5=eyo&6_8TY4t^3NY<>I?lK+TCy%fEe!alz`q#UfNG9JpA7KvWhg7qg{e zJik@Qq_qJ4WV?lnalqB*U%2>y!9k`v|Jt5(XDl(4f@6(q6&}vV`~}psC=ZKqF1z#a zhX@CLgP4pKA%9=fAXme~+`8cWX{Ep03Z?ou@Gv8(K2?9i!_$uvqe@74m~@nS#90jw ze?gkF-l3gecOv`{+j#EKw^)d^KDzTOM{~qYUwL@Pj{J(rHr87_9RA<1jb9Iae}L^g zytji)$yy(jJo~!_4`MxbC5)VNn@q&|q8b+5}ydYLjXTk;PQOd$rDG`velv*+Cg(zcQ zEy_dqtCd`n6J%OhB0w9@V>6*7f9yph27d-iupq+AjSzN%1=zw16|=Yr7Q}pE#q}mu z5TMG%fy{5B+(fu^p`)b(Z-GFD;;jj3g0^WffFGm~7;gOzi;qUv)(6;ZDyyJA?95=Q zfaEtwyO0LY`Y77(HcY4>T_BYc!$>L>A{@gwi6%J0snlTYNVZQ-o!Y*{3ho2Tn30GT z!b?1YRWhVLfT7q?ZnQA?1PdF($s^qMOs_B{>usoTfL5e1y^^CeP{_DG^}7NE0X#GX zU<(w)l0*_qeL&`%Re)F@zy#r@-Z0%F1^J0XZ&oHdSRWvQA6m^@@zTG|EVqD03SfL$i{dnm6xd4HaF9d_2V{#BzGAHp_<|WJWGw2#+<^%d zG6(%Kk*N<5%kOcnP$6uQkYt4ljD(W&GP}rx3X$pzwM7a$)IdQ!B8zdtLC*#Xu_B6N zqVNe6oCBAM%oa3Ia4{$r{@%(1lp^Zbl#a#;%t}3}yXj6+PsrDhJ>xv4Ibp(GuOUEc z1N@T%R?X%_2@45>O`Nc>vsz%5p;G+!=El z>H|Lq6%Za~eAL}1*EAqq%m|afPgL^3QuM>T@=h@CpP{#NGK_AtU_^w>uGKnbg3%my zV;ANCEzblCP*d;s#|0H3jZ`7Vx%L!841?{hITU!G1a=0G)IolSt>1sdn~C-E1uBlr z`URf}&UzBap#Y?tOD4ad*iNdzPvU>l63p47sv#8qcx5{oZYu5Zb5!I=>|>2F%_IZsWkfOHDR9q1tTus)(Eq#6rPNV5&@F7jnJ{*BuY zbn}>o>59x<0FMGlz%B&)i5Jmlf)^I~U}YAU9WdoFO4!lI68TIpZI}WsMec;8bOYg{P>HTtr_9cGDSJJpw;?Cr5;Hu+B2C1dBjBXQ{^yzxg#VQwCzOL{Lf= ztx5Ze#j~2eQgmZ(%^G{i%;0|>W6dn`N^qoP{iqJxZT>=x;#a?k2>ME}vp~bC&yW|K zE=F=a$#D?MW8&<_ti81tH`>!)Nrf6w$t{0Jo;66{5F8FRg23U}ifu;4I01y&H&@(v zjwCw)7$6%6t8wGnvG8Ji+5>WOf)!%10?YVC7`jBBcY-Ne#@ERq2y$n9AVT?`-($kQ z6MR67ZyA#H?XAH2cHIHPZ0RD4P?fAkK}?YR%!=a5%GSISoSCLklL2%~yg@IzTb-K~ z$2C(KQ=O@6CS7=tBCVhc1YV>Y^$D^mh0(D~_ac`ZMB4T9Zy%ItJ7`qBoU;8%_qorC zhde!ggfWVxEj+D9LR!UWxeP+2d!PAbR3wGhR6|z06(9Sf0GmxS^P^=nmm`)zGPS>Q zoypm7DCRI)LQ-4~?G2cm=deID{fytrE5_QeQTAaSavoZB#wW5ZgH7JTZ{)YaHS4`d zw}19W*i41tJfT54u@W#0bjPjr=@20jh_KqixVMSn@+1uezZiA|p(LXlXsw+R4#rha z`?1kv>4T6Nb#!G*6G)pu5D0vhcH1D5e0YufK+wR#`orhY^iAc8&7^SbETuh69Nmwz zz({L#1@bwJMvg8u5b-FkcwQ98G0Qa&2>AlNh4ncY4JlZ^WZp@Epg5!fX3KI|+ib|J zZif~LW*}K@$OJN<+np1znbbz|ZQ`?4`|1j?2i;O6FhN~;nPMo;RrK9s##zYXLUgT!DQx)C0W6Kl#7$m3eAgGu#~JDV8Anl=W@&!XKeM#unbK$O4DUra}S5{U96cqI2QiS6K9 z=cirQo5Ge*u#fHm;fW^)LVSI9{eZA(y>I^&v)<7uv?pW1s4)~EL>p2KolXiiZ3s4$ zSv0?o={g75DkpdBGNyrl}749CUMNeU!3Ub>BE zK?tcmCtQdkwm4#8%zeX_2HY8gowv3ERWKL}ltt0HE}DOiE<8XwvAT4tne*Zr1Xb@y2#h5p?FY$r2%6e#3@v|Im0>b$)X4l9 z)?cP*bS(zl>8*C54 z5x-+2KK^RVjzCFtThz$3`nm^ZT(CvUZ3x8|#%P45-Gu{29@cN-(6Eloi{1fXFKGv# zSw-&x(b&1s1BS2fa!3v3r)pt1qEi{pQwX!rAe`}cA4qZX&O4aFuT7`b+mV0|H5i;`DJoWZ3g-j|E9)wiQw3*=F^WQu~##qw|gO}_T2>Jj&Q`KMLn^#E0a7Gk9{W#u!A@)Wb z(@`M7;)?WGwDHSgYoFwBVRG^v>gy8tE`JqlxpQS(qTL&LKVZPA(T}o4W&n;=oT>TUPDFo z=4wIKnx6y!!H4iVsT4UJphGOh0vEixKNdpTezh4jHvja`fByW#pZ|^r@b?e1jVKe3 z75R32@b~B6fBXEyf92o**WW*hP;fr{adZ9$Kd*mM$P<5*zg%E`qYmqcOo0%(_vb!O z{s)N1CjM^O`20*B%)9JDoYk>2-v9P_QxV>9OE=!Zraao8u_LCAyFz+FpFjx9n?}_8 z5O9l7hH?9(LfPLd@RzDE;hfGuVXKz8;o6^X&0EbNFgWJGR>t=L_*t|T@U4OLdOlIU zfl|rpm5=-u1iF@l2jzeuuD?GT%1Z`%aQYXNOVVvr0RU5*M$C~(GBr-E{2DyRk9LzQ z0^^Y!e1hY}!KL^lW3UpzN z#QO0_gj_w(yty-wK_tpjL?+|!A)*LWbQ?=*h9T951i3-k4FNCUGQ+iH<8!i%o9ql)qCX(_akzq%wrr1aH!^X5CTvC*3!`MOa z6VfITFZ)0%7`p5q83{rg#7Ze@i~g;wuYax(x_1n~KNA_`BzGSWgaTi5pcBbwL7hi9KRN7fnHA=8RLi8(`#dbJ}!k zKvI!6+m~VZ1-8yGd;CEx1jFlOZETPr#Bc^AOUL@0FhhPT>;z}fhC9sY`^9kpY>yKh zt^~lFS8RmBxj5+5xJvTH+y*!KSs)NZ0B#bwQUJ|MW1FCWkqf{efmDt}7-xj6e?>l( zx&HZArzIvhlP(il|D56AI$ zOaW93S8Dw>djTZp0@R``-WI6YsRYE-ZpH~rIf=Qd0RxFF(J%a!|EanB!+-z$;~zi& z@Xvq#Z~ypt`~1_NKL6n#H~Wdja^*^a)#xUu)ld>W1e1H7qZFK;6c)DWK=eg9+@G>eeq#;^K?HO`hNN4p;4n`fPOCLm*0MQXd(5PiXz?Z0)<9Ir#nmpZ9L5J2pA^lUZMWS*PY{Kq^^(I`pq^qwmR( zVf(q9|9=>ocTG6J|GAw1I$!5XvkUE?lram&YfQ-+=U`^v zzaTj0o+7p1v@&5w*h(NdJsHbW4FX;flC4DWI2x@(1tI4^7^Lgr*+R#nNSp8+j0zt1 zlX=388&u2vVX$t5|_Kb^la5xBIH|tGW-Isen z2wIqt88%a-?PncZFu%roz_=a0D`YE<_FAQpkORQH{Tbb zSdz#`56oG4QA8xanl^LyEQzZqIPzvJ`45s-wldG>6?CY=OUteg57dWu><6Vdjt<7J z1UdSmC$0J;Bj#y+l+1o2p%v^!Vd{p_kC@TmB%|j|I{DlK4-u}#cFkDwV$7Y@Fe`cA z1Q@HKoW8qGfoAuCUmr3!vlW}`9E_qEtNe*5>=+qsIrJh6(Tp8ft*p;*{3NVxLoh4? zt?~|r{Au~8h__>j<$*A>PM*k!W6(?ilk4BaXflVnijWR*G;QrP4T(+YNHaA%|Le1s z9|E~bATrb(WOjk)s?u#*F%cC>#u)}4CgW>3;@Jc!rca(ZdB?+5dmB2F=A1AHu}1fR zyKkK&XG9*AWM4>CvRHJOKXe}&k-<;D4M|aev)o$k#63XVk@$hYMN6E+alIYqt&gIHwIq@>h&Ukg_JXv-`1W zWsa5fpZ&PQ-c6|D1mX9bcBPoHOsi#d^p`G)E1|T;)gqBr3`w}n5P|ueoS4rtvQw&P zFUx)hh0W|a=E`OyyE1+vRr%H5W^$rrr1>7K$RT3@HmaF6u&lYLbkZ+%n-8WzO`O^>?w%U6)1Q3;)HCRJY%&UqJo{`*WM$!*(E2u!!q+qG7D|3#pDmRle zhqS@nd8_c4bgp#EFBTaK@c+|i@m(Pm(#~fdj-rmm5Rh;yX7hJgh@DqXNaApms=Ag; z-2LG4I5h~VLSGmNwA558N$7NLQiE_0INT4brGCpt&8hZtEY4NXzj++KvK4A@d7QIW zbg$B;Lm0o)7t=8&ELuIx0XeN;-!Pjx-lM05z$UoG-c{h@_TCE2@^EfU?-=~Uh6r?U zShBCD%@itJ^LiRs<_Ssrcv_YchGM}8dDd%|q&7Q;f}skM7v9K0cqnH*!Nv#pn&k9# zU>9dFaSL?x8#fHU!~|fuY(|1&+DDjWz12ppml>2{J15k{BXY2&%~e%+trAJiftr$W z@zmpDE((BPwIuiuX$OXq?KX`{arn%k2GjQ9cn;6C4cjz?H=bB4? z;$p327=?bbT!6(*Lz8gR@SJpW+GSOGm3iTcLDbYiBl#LS3)y68O!hN=%3vaRN{)u9 z&C_Xo5EY581X)j~f#G-9iesldg~d#j%QW^K=>*NlfE8^#}iGE1oBy z5h^Axr)|K_(MM<)dV?$~_Bk*BM}{*nlN=iDpO?eu*)$q;l4!_2o3Qli4U(>@|rsz9Oakk=;5-%cZipg zAHytu9TI=x;I%+F+>~NUh5W|UZfL4xW~kAfg67h~(iJ_IAq6ZhZ20~Yb2Hux_uGv@ zz!vY%&Ui0Q#=J~%oq82sEP0XHW`1CfO$p4c)|WST2C#POKruw? z6!dQmC&(k+%%WD&ofr>hqEwDyS&T*tS_e3V>5rjCd3mWKcs1%5Kjo*nFxA9JYmXUG^rEfY2vK8=5RthA*4Y} z_Jnw%fqFh!X{KC=Cq#DP_L~q-w3O``Lu`3P7Bn{vH|73{qL+YcseDDGl;Vsz9p= z@_Zm0+8qM;U>;{Lv!wL*=JeVA(5%+J~0Vc2<77O}G7+VwoVJaB;6b4V&J z7ui0XFi#jk-z+m-5j%uY*)%!Xg?YXpC(LuP*@sKtr=u}hF|`phIcKOaPsnJYJ&hCQ z3G+J1xpMj@%ri4D9?|(P%%h*F_#EpXC(IL9PjtCf6Xpr&(NsWDp5b6xdc{;xo}p6_ z5`JAm=|p+j8SISau!-`tp&mn+I;1F%v525@(8tCqTduu|LFX4U_^?)pOufWX5=)Td z136Ki8R|3wwNRSo?MSdOTX*ncn91vj^3-NZWsHVmw+xpRUp$5-yK?(g=_0{jlxHMm z6S^^;fHiEXis7a-gaF^@4rA-$U&gcY{{hREME{O6SmI#{%hhcnJVXDHnUEqpSF(+S zN=p~vX)pP>O^~24_dytmZOIeisg?A(RzDG*QSS3Z_#$I(K&q-Y9XG%eJ24)bXlibG zPGWaVFaaLqFBp=50u}A>M0@DM_E<-8^o^PtU9b z6>8Po6zfw(9Gg!N)chs5>1Rdb{cCe8e!>)ui1LgXA%V(M&pX_q%~bq^h)67V%qt+w z0CTRIx{dB}0S11tG0waWb1~7#U;iYIB^AymXr3knjTtsYvlzF9Fxrp&ctR)3DiPu~ zV$%sLnN6FlV5@@9l-ZVyXls&vQ}NSm)cx8vq;T=72p~l>HDPZ3Or%`mKC%|M9dgsY zGFm^_KgxW6uqxd%eHWP$KB7+~>L`8;M3uz)Fc(#gWmZ%1^Vd||Q4?G76C#as*LN#^ z`i_GXu;!UnPysp!)4P!p@~Kpa>|;!jdYdXfi*->qrpDfWG>0B6T&QCd)Z2~enygx^ z61Xr-BA0zydFa{_`AS})LrB%0D9^NfP~q9@Bwjn(v9l+RKT{Xy-vt(c?X;VGm3-!e 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