En este tercer capítulo comenzaremos con el bloque Math. El mismo abarca problemas de aritmética en Solidity, tales como divisiones de enteros, overflows y underflows. En este artículo intentaré explicar los desafíos:
1. Token sale2. Token whale
3. Retirement fund
1. Token sale
Problema:
"Este contrato permite que puedas comprar o vender tokens a un tipo de cambio donde 1 Token = 1 Ether.El contrato comienza con un saldo de 1 Ether. Ve si puedes robar algo de eso."
El contrato que atacaremos será el siguiente
pragma solidity ^0.4.21;
contract TokenSaleChallenge {
mapping(address => uint256) public balanceOf;
uint256 constant PRICE_PER_TOKEN = 1 ether;
function TokenSaleChallenge(address _player) public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance < 1 ether;
}
function buy(uint256 numTokens) public payable {
require(msg.value == numTokens * PRICE_PER_TOKEN);
balanceOf[msg.sender] += numTokens;
}
function sell(uint256 numTokens) public {
require(balanceOf[msg.sender] >= numTokens);
balanceOf[msg.sender] -= numTokens;
msg.sender.transfer(numTokens * PRICE_PER_TOKEN);
}
}
Datos de lectura:
Ideas para soluciones:
Explicación:
Test a ejecutar:
Conclusión:
Es importante estar atento a este tipo de problemas cuando realizamos sumas, restas o multiplicaciones. Sin embargo, a partir de la versión 0.8.0 de Solidity, el compilador incorporó chequeos para solucionar este tipo de inconvenientes. No obstante, versiones anteriores mitigaron el problema incorporando librerías de terceros a sus contratos (como SafeMath de Openzeppelin), cuya función era y es la de realizar los mismos chequeos.
2. Token whale
Problema:
"Este token compatible con el estándar ERC20 es difícil de adquirir. Hay un límite de 1000 tokens existentes, los cuales son todos tuyos para comenzar.Encuentra un camino para acumular al menos 1.000.000 de tokens para superar este desafío."
El contrato que atacaremos será el siguiente
pragma solidity ^0.4.21;
contract TokenWhaleChallenge {
address player;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name = "Simple ERC20 Token";
string public symbol = "SET";
uint8 public decimals = 18;
function TokenWhaleChallenge(address _player) public {
player = _player;
totalSupply = 1000;
balanceOf[player] = 1000;
}
function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000;
}
event Transfer(address indexed from, address indexed to, uint256 value);
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
}
function transfer(address to, uint256 value) public {
require(balanceOf[msg.sender] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
_transfer(to, value);
}
event Approval(address indexed owner, address indexed spender, uint256 value);
function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);
allowance[from][msg.sender] -= value;
_transfer(to, value);
}
}
Datos de lectura:
Ideas para soluciones:
Explicación:
Test a ejecutar:
Conclusión:
Cuando se utilizan funciones auxiliares para lograr mayor comprensión o reutilización de código, es sumamente importante tener en cuenta qué valores pueden adoptar las variables globales a lo largo del flujo completo de una transacción. En este caso, no se contempló que podía ser una cuenta manipulada por la misma persona dueña los tokens iniciales.
3. Retirement fund
Problema:
"Este fondo de jubilación es lo que los economistas llaman un dispositivo de compromiso. Estoy tratando de asegurarme que al momento de jubilarme, tendré 1 ether.He comprometido 1 ether al siguiente contrato, y no lo retiraré hasta que hayan pasado 10 años. Si llegara a retirarlo antes, el 10% de mi ether irá a un beneficiario (¡usted!).
Realmente no quiero que te quedes con parte de mi jubilación, así que estoy decidido a dejar esos fondos solo hasta dentro de 10 años. ¡Buena suerte!"
El contrato que atacaremos será el siguiente
pragma solidity ^0.4.21;
contract RetirementFundChallenge {
uint256 startBalance;
address owner = msg.sender;
address beneficiary;
uint256 expiration = now + 10 years;
function RetirementFundChallenge(address player) public payable {
require(msg.value == 1 ether);
beneficiary = player;
startBalance = msg.value;
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function withdraw() public {
require(msg.sender == owner);
if (now < expiration) {
// early withdrawal incurs a 10% penalty
msg.sender.transfer(address(this).balance * 9 / 10);
} else {
msg.sender.transfer(address(this).balance);
}
}
function collectPenalty() public {
require(msg.sender == beneficiary);
uint256 withdrawn = startBalance - address(this).balance;
// an early withdrawal occurred
require(withdrawn > 0);
// penalty is what's left
msg.sender.transfer(address(this).balance);
}
}
Datos de lectura:
Ideas para soluciones:
Explicación:
Test a ejecutar:
Conclusión:
Hay que ser muy cuidadosos a la hora de aplicar lógica basada en el balance de nuestro contrato. Sumado al overflow que se generó al romper la relación entre el balance inicial y el actual, muchos otros vectores de ataque podrían aparecer si no tenemos los recaudos suficientes para recibir ether.
Que tengas una noche genial.
No te alteres.
bengalaQ