Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

article: EIP-1153: Transient storage opcodes #45

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
455 changes: 455 additions & 0 deletions EIPs/eip-1153/README.md

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions EIPs/eip-1153/contracts/ERC20WithTempApprove.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
*
* ///////////////////////////////////////////////////////////////
* ВАЖНО!
* ///////////////////////////////////////////////////////////////
*
* @notice Код написан для демонстрации возможностей transient storage,
* используется исключительно в этих целях и не проходил аудит
* Не использовать в mainnet с реальными средствами!
*
*/
contract ERC20WithTempApprove is ERC20 {
error ExternalCallFailed();

constructor() ERC20("Test", "T") {}

/// @notice Функция для вызова внешнего смарт-контракта с выдачей ему разрешения на списание токенов
function approveAndCall(address spender, uint256 value, bytes memory data) external {
// Выдаем временный апрув только на ту сумму которую планируем потратить
_temporaryApprove(spender, value);

// Выполняем внешний вызов к смарт-контракту который спишет токены
(bool success,) = address(spender).call(data);
if (!success) {
revert ExternalCallFailed();
}
}

/// @notice Функция для выдачи временного разрешения
function _temporaryApprove(address spender, uint256 value) private {
// Формируем ключ для записи в transient storage
// записываем в него адрес владельца токенов,
// адрес контракта, который их спишет
// и само значение
bytes32 key = keccak256(abi.encode(msg.sender, spender, value));

// Записываем одобренное количество токенов по сформированному ключу
assembly {
tstore(key, value)
}
}

/**
* @notice Когда целевой смарт-контракт вызовет transferFrom
* transferFrom задействует функцию _spendAllowance
* поэтому здесь мы проверим было ли выдано временное разрешение
*/
function _spendAllowance(address owner, address spender, uint256 value) internal override {
// для начала восстановим ключ
bytes32 key = keccak256(abi.encode(owner, spender, value));

// Получаем значение по ключу
uint256 temporaryApproval;
assembly {
temporaryApproval := tload(key)
}

// Если одобрение есть, перевод токенов будет выполнен
// если нет, передаем выполнение стандартной функции
// для проверки ранее выданных разрешений
if (temporaryApproval > 0) {
// Проверка соответствует ли временное разрешение тому value
// которое будет потрачено - не имеет смысла
// потому что в таком случае ключ не совпадет

// Обязательно очищаем переходное хранилище!
assembly {
tstore(key, 0)
}
} else {
super._spendAllowance(owner, spender, value);
}
}

function mint(address account, uint256 amount) external {
_mint(account, amount);
}
}
59 changes: 59 additions & 0 deletions EIPs/eip-1153/contracts/ExampleWithBoolReentrancyLock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

/**
*
* ///////////////////////////////////////////////////////////////
* ВАЖНО!
* ///////////////////////////////////////////////////////////////
*
* @notice Код написан для демонстрации возможностей transient storage,
* используется исключительно в этих целях и не проходил аудит
* Не использовать в mainnet с реальными средствами!
*
*/
contract ExampleWithBoolReentrancyLock {
// Создаем переменную _lock
bool private _lock;

// Заводим маппинг для отслеживания балансов
mapping(address account => uint256 amount) private _balances;

error InsufficientBalance();
error ReentrancyAttackPrevented();
error TransferFailed();

function withdraw(uint256 amount) external {
// Перед выполнением функции проверяем, что это не повторный вход
if (_lock) {
revert ReentrancyAttackPrevented();
}
// Блокируем функцию для защиты от повторного входа
_lock = true;

// Проверяем текущее состояние
if (_balances[msg.sender] < amount) {
revert InsufficientBalance();
}

// Изменяем состояние
_balances[msg.sender] -= amount;

// Переводим запрошенные средства
(bool success,) = msg.sender.call{value: amount}("");
if (!success) {
revert TransferFailed();
}

// Выключем блокировку функции
_lock = false;
}

function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}

receive() external payable {
_balances[msg.sender] += msg.value;
}
}
47 changes: 47 additions & 0 deletions EIPs/eip-1153/contracts/ExampleWithReentrancyGuard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
*
* ///////////////////////////////////////////////////////////////
* ВАЖНО!
* ///////////////////////////////////////////////////////////////
*
* @notice Код написан для демонстрации возможностей transient storage,
* используется исключительно в этих целях и не проходил аудит
* Не использовать в mainnet с реальными средствами!
*
*/
contract ExampleWithReentrancyGuard is ReentrancyGuard {
mapping(address account => uint256 amount) private _balances;

error InsufficientBalance();
error ReentrancyAttackPrevented();
error TransferFailed();

function withdraw(uint256 amount) external nonReentrant {
// Проверяем текущее состояние
if (_balances[msg.sender] < amount) {
revert InsufficientBalance();
}

// Изменяем состояние
_balances[msg.sender] -= amount;

// Переводим запрошенные средства
(bool success,) = msg.sender.call{value: amount}("");
if (!success) {
revert TransferFailed();
}
}

function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}

receive() external payable {
_balances[msg.sender] += msg.value;
}
}
77 changes: 77 additions & 0 deletions EIPs/eip-1153/contracts/ExampleWithTransientReentrancyLock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
*
* ///////////////////////////////////////////////////////////////
* ВАЖНО!
* ///////////////////////////////////////////////////////////////
*
* @notice Код написан для демонстрации возможностей transient storage,
* используется исключительно в этих целях и не проходил аудит
* Не использовать в mainnet с реальными средствами!
*
*/
contract ExampleWithTransientReentrancyLock {
// Заводим константное значение для адресации в transient storage
// keccak256("REENTRANCY_GUARD_SLOT");
bytes32 constant REENTRANCY_GUARD_SLOT = 0x167f9e63e7ffa6919d959c882a4da1182dccfb0d790328477621b65d1978856b;

mapping(address account => uint256 amount) private _balances;

error InsufficientBalance();
error ReentrancyAttackPrevented();
error TransferFailed();

modifier nonReentrant() {
// Перед выполнением функции проверяем, что это не повторный вход
if (_tload(REENTRANCY_GUARD_SLOT) == 1) {
revert ReentrancyAttackPrevented();
}
// Записываем по ключу REENTRANCY_GUARD_SLOT значение 1
_tstore(REENTRANCY_GUARD_SLOT, 1);

_;

// Очищаем значение ключа в transient storage после внешнего вызова
_tstore(REENTRANCY_GUARD_SLOT, 0);
}

function withdraw(uint256 amount) external nonReentrant {
// Проверяем текущее состояние
if (_balances[msg.sender] < amount) {
revert InsufficientBalance();
}

// Изменяем состояние
_balances[msg.sender] -= amount;

// Переводим запрошенные средства
(bool success,) = msg.sender.call{value: amount}("");
if (!success) {
revert TransferFailed();
}
}

/// @notice Вспомогательная функция для записи в transient storage
function _tstore(bytes32 location, uint256 value) private {
assembly {
tstore(location, value)
}
}

/// @notice Вспомогательная функция для чтения из transient storage
function _tload(bytes32 location) private view returns (uint256 value) {
assembly {
value := tload(location)
}
}

function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}

receive() external payable {
_balances[msg.sender] += msg.value;
}
}
Binary file added EIPs/eip-1153/img/eips-history.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added EIPs/eip-1153/img/frames.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added EIPs/eip-1153/img/opcodes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added EIPs/eip-1153/img/storage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added EIPs/eip-1153/img/tstorage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added EIPs/eip-1153/img/types-of-storage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added EIPs/eip-1153/img/used-gas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Join our TG-channel: [MetaLamp|Web3 DevTeam](https://t.me/metametalamp)

- [EIP-140: REVERT instruction](./EIPs/erc-140/README.md)
- [ERC-165: Standard Interface Detection](./EIPs/erc-165/README.md)
- [EIP-1153: Transient storage opcodes](./EIPs/eip-1153/README.md)
- [ERC-1363: Payable Token(transferAndCall)](./EIPs/erc-1363/README.md)
- [ERC-4337: Account Abstraction Using Alt Mempool](./EIPs/erc-4337/README.md)
- [ERC-4626: Tokenized Vaults](./EIPs/erc-4626/README.md)
Expand Down