diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..086785858f --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +RPC_MAINNET="https://eth.llamarpc.com" +# RPC_MAINNET="https://mainnet.infura.io/v3/API-KEY" \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 84bd80a031..c72beb8d35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,10 @@ jobs: - name: Run tests run: forge test -vvv + env: + RPC_MAINNET: ${{ secrets.RPC_MAINNET }} - name: Run snapshot - run: forge snapshot \ No newline at end of file + run: forge snapshot + env: + RPC_MAINNET: ${{ secrets.RPC_MAINNET }} \ No newline at end of file diff --git a/README.md b/README.md index 00d72d1e86..56e1760c61 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ To generate the docs, run `npx hardhat docgen` (you may need to run `npm install ### Run Tests +Prior to running tests, you should set up your environment. At present this repository contains fork tests against ETH mainnet; your environment will need an `RPC_MAINNET` key to run these tests. See the `.env.example` file for an example -- two simple options are to copy the LlamaNodes RPC url to your `env` or use your own infura API key in the provided format. + +The main command to run tests is: + `forge test -vv` ### Run Static Analysis diff --git a/foundry.toml b/foundry.toml index 5342486960..5267621cfd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -22,4 +22,7 @@ via_ir = false # Override the Solidity version (this overrides `auto_detect_solc`) solc_version = '0.8.12' +[rpc_endpoints] +mainnet = "${RPC_MAINNET}" + # See more config options https://github.com/gakonst/foundry/tree/master/config \ No newline at end of file diff --git a/src/contracts/strategies/StrategyBase.sol b/src/contracts/strategies/StrategyBase.sol index c2faeb6b40..9a72c387c6 100644 --- a/src/contracts/strategies/StrategyBase.sol +++ b/src/contracts/strategies/StrategyBase.sol @@ -80,11 +80,15 @@ contract StrategyBase is Initializable, Pausable, IStrategy { * @notice calculation of newShares *mirrors* `underlyingToShares(amount)`, but is different since the balance of `underlyingToken` * has already been increased due to the `strategyManager` transferring tokens to this strategy prior to calling this function */ - uint256 priorTokenBalance = _tokenBalance() - amount; - if (priorTokenBalance == 0 || totalShares == 0) { + if (totalShares == 0) { newShares = amount; } else { - newShares = (amount * totalShares) / priorTokenBalance; + uint256 priorTokenBalance = _tokenBalance() - amount; + if (priorTokenBalance == 0) { + newShares = amount; + } else { + newShares = (amount * totalShares) / priorTokenBalance; + } } // checks to ensure correctness / avoid edge case where share rate can be massively inflated as a 'griefing' sort of attack diff --git a/src/test/DepositWithdraw.t.sol b/src/test/DepositWithdraw.t.sol index 3f16fdb077..9f03216335 100644 --- a/src/test/DepositWithdraw.t.sol +++ b/src/test/DepositWithdraw.t.sol @@ -3,6 +3,7 @@ pragma solidity =0.8.12; import "./EigenLayerTestHelper.t.sol"; import "../contracts/core/StrategyManagerStorage.sol"; +import "./mocks/ERC20_OneWeiFeeOnTransfer.sol"; contract DepositWithdrawTests is EigenLayerTestHelper { uint256[] public emptyUintArray; @@ -410,6 +411,154 @@ contract DepositWithdrawTests is EigenLayerTestHelper { } + function testDepositTokenWithOneWeiFeeOnTransfer(address sender, uint64 amountToDeposit) public fuzzedAddress(sender) { + // MIN_NONZERO_TOTAL_SHARES = 1e9 + cheats.assume(amountToDeposit >= 1e9); + + uint256 initSupply = 1e50; + address initOwner = address(this); + + ERC20_OneWeiFeeOnTransfer oneWeiFeeOnTransferToken = new ERC20_OneWeiFeeOnTransfer(initSupply, initOwner); + IERC20 underlyingToken = IERC20(address(oneWeiFeeOnTransferToken)); + + // need to transfer extra here because otherwise the `sender` won't have enough tokens + underlyingToken.transfer(sender, 1000); + + IStrategy oneWeiFeeOnTransferTokenStrategy = StrategyBase( + address( + new TransparentUpgradeableProxy( + address(baseStrategyImplementation), + address(eigenLayerProxyAdmin), + abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg) + ) + ) + ); + + _testDepositToStrategy(sender, amountToDeposit, underlyingToken, oneWeiFeeOnTransferTokenStrategy); + } + + /// @notice Shadow-forks mainnet and tests depositing stETH tokens into a "StrategyBase" contract. + function testForkMainnetDepositSteth() public { + // hard-coded inputs + address sender = address(this); + uint64 amountToDeposit = 1e12; + + // shadow-fork mainnet + uint256 forkId = cheats.createFork("mainnet"); + cheats.selectFork(forkId); + + // cast mainnet stETH address to IERC20 interface + IERC20 steth = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); + IERC20 underlyingToken = steth; + + // deploy necessary contracts on the shadow-forked network + // deploy proxy admin for ability to upgrade proxy contracts + eigenLayerProxyAdmin = new ProxyAdmin(); + //deploy pauser registry + eigenLayerPauserReg = new PauserRegistry(pauser, unpauser); + /** + * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are + * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. + */ + emptyContract = new EmptyContract(); + delegation = DelegationManager( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + strategyManager = StrategyManager( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + slasher = Slasher( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + eigenPodManager = EigenPodManager( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + delayedWithdrawalRouter = DelayedWithdrawalRouter( + address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + ); + + address[] memory initialOracleSignersArray = new address[](0); + beaconChainOracle = new BeaconChainOracle(eigenLayerReputedMultisig, initialBeaconChainOracleThreshold, initialOracleSignersArray); + + ethPOSDeposit = new ETHPOSDepositMock(); + pod = new EigenPod(ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, REQUIRED_BALANCE_WEI); + + eigenPodBeacon = new UpgradeableBeacon(address(pod)); + + // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs + DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher); + StrategyManager strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher); + Slasher slasherImplementation = new Slasher(strategyManager, delegation); + EigenPodManager eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, slasher); + DelayedWithdrawalRouter delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager); + // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. + eigenLayerProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(delegation))), + address(delegationImplementation), + abi.encodeWithSelector( + DelegationManager.initialize.selector, + eigenLayerReputedMultisig, + eigenLayerPauserReg, + 0/*initialPausedStatus*/ + ) + ); + eigenLayerProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(strategyManager))), + address(strategyManagerImplementation), + abi.encodeWithSelector( + StrategyManager.initialize.selector, + eigenLayerReputedMultisig, + eigenLayerReputedMultisig, + eigenLayerPauserReg, + 0/*initialPausedStatus*/, + 0/*withdrawalDelayBlocks*/ + ) + ); + eigenLayerProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(slasher))), + address(slasherImplementation), + abi.encodeWithSelector( + Slasher.initialize.selector, + eigenLayerReputedMultisig, + eigenLayerPauserReg, + 0/*initialPausedStatus*/ + ) + ); + eigenLayerProxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(eigenPodManager))), + address(eigenPodManagerImplementation), + abi.encodeWithSelector( + EigenPodManager.initialize.selector, + beaconChainOracle, + eigenLayerReputedMultisig, + eigenLayerPauserReg, + 0/*initialPausedStatus*/ + ) + ); + + // cheat a bunch of ETH to this address + cheats.deal(address(this), 1e20); + // deposit a huge amount of ETH to get ample stETH + (bool success, bytes memory returnData) = address(steth).call{value: 1e20}(""); + require(success, "depositing stETH failed"); + returnData; + + // deploy StrategyBase contract implementation, then create upgradeable proxy that points to implementation and initialize it + baseStrategyImplementation = new StrategyBase(strategyManager); + IStrategy stethStrategy = StrategyBase( + address( + new TransparentUpgradeableProxy( + address(baseStrategyImplementation), + address(eigenLayerProxyAdmin), + abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg) + ) + ) + ); + + _testDepositToStrategy(sender, amountToDeposit, underlyingToken, stethStrategy); + + } + function _whitelistStrategy(StrategyManager _strategyManager, StrategyBase _strategyBase) internal returns(StrategyManager) { // whitelist the strategy for deposit cheats.startPrank(strategyManager.owner()); diff --git a/src/test/mocks/ERC20_OneWeiFeeOnTransfer.sol b/src/test/mocks/ERC20_OneWeiFeeOnTransfer.sol new file mode 100644 index 0000000000..e660d6b8d2 --- /dev/null +++ b/src/test/mocks/ERC20_OneWeiFeeOnTransfer.sol @@ -0,0 +1,617 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.12; + + +// copy-pasted OZ code with _balances mapping made *internal* instead of private + + + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +// pragma solidity ^0.8.0; + +/** + * @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 OpenZeppelin_Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) + +// pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface OpenZeppelin_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 amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `from` to `to` using the + * allowance mechanism. `amount` 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 amount) external returns (bool); +} + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +// pragma solidity ^0.8.0; + +// import "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface OpenZeppelin_IERC20Metadata is OpenZeppelin_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); +} + +// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol) + +// import "./IERC20.sol"; +// import "./extensions/IERC20Metadata.sol"; +// import "../../utils/Context.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}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * 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}. + */ +contract OpenZeppelin_ERC20 is OpenZeppelin_Context, OpenZeppelin_IERC20, OpenZeppelin_IERC20Metadata { + mapping(address => uint256) internal _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @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 override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override 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 override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override 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 `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` 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 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + 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 `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + 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 + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `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. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer(address from, address to, uint256 amount) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. + _balances[to] += amount; + } + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + unchecked { + // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. + _balances[account] += amount; + } + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + // Overflow not possible: amount <= accountBalance <= totalSupply. + _totalSupply -= amount; + } + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` 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. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} +} + + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol) + +// pragma solidity ^0.8.0; + +// import "../ERC20.sol"; +// import "../../../utils/Context.sol"; + +/** + * @dev Extension of {ERC20} that allows token holders to destroy both their own + * tokens and those that they have an allowance for, in a way that can be + * recognized off-chain (via event analysis). + */ +abstract contract OpenZeppelin_ERC20Burnable is OpenZeppelin_Context, OpenZeppelin_ERC20 { + /** + * @dev Destroys `amount` tokens from the caller. + * + * See {ERC20-_burn}. + */ + function burn(uint256 amount) public virtual { + _burn(_msgSender(), amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, deducting from the caller's + * allowance. + * + * See {ERC20-_burn} and {ERC20-allowance}. + * + * Requirements: + * + * - the caller must have allowance for ``accounts``'s tokens of at least + * `amount`. + */ + function burnFrom(address account, uint256 amount) public virtual { + _spendAllowance(account, _msgSender(), amount); + _burn(account, amount); + } +} + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/presets/ERC20PresetFixedSupply.sol) +// pragma solidity ^0.8.0; + +// import "../extensions/ERC20Burnable.sol"; + +/** + * @dev {ERC20} token, including: + * + * - Preminted initial supply + * - Ability for holders to burn (destroy) their tokens + * - No access control mechanism (for minting/pausing) and hence no governance + * + * This contract uses {ERC20Burnable} to include burn capabilities - head to + * its documentation for details. + * + * _Available since v3.4._ + * + * _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._ + */ +contract OpenZeppelin_ERC20PresetFixedSupply is OpenZeppelin_ERC20Burnable { + /** + * @dev Mints `initialSupply` amount of token and transfers them to `owner`. + * + * See {ERC20-constructor}. + */ + constructor(string memory name, string memory symbol, uint256 initialSupply, address owner) OpenZeppelin_ERC20(name, symbol) { + _mint(owner, initialSupply); + } +} + + + +// actual mock code +contract ERC20_OneWeiFeeOnTransfer is OpenZeppelin_ERC20PresetFixedSupply { + + constructor(uint256 initSupply, address initOwner) + OpenZeppelin_ERC20PresetFixedSupply("ERC20_OneWeiFeeOnTransfer_Mock", "ERC20_OneWeiFeeOnTransfer_Mock", initSupply, initOwner) + {} + + /** + * @dev Moves `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. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer(address from, address to, uint256 amount) internal virtual override { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + if (amount != 0) { + unchecked { + _balances[from] = fromBalance - amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. + _balances[to] += (amount - 1); + } + } + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + +} \ No newline at end of file