# Team HW5
作業連結:https://github.com/DeFiHackLabs-BootCamp/Fall-HW5-Team

## Note
- NFT 的能力值只能透過 Training 合約來更新
## 漏洞分析
### RequestQuest.sol
1. Line-3 `_safeMint()` 會造成 Reentrancy attack, 使用合約能不斷重複呼叫 `mintChiikawa()`
```solidity=
function mintChiikawa() public {
uint256 tokenId = _nextTokenId++;
_safeMint(msg.sender, tokenId);
// Initialize metadata for the minted token
chiikawaStats[tokenId] =
ChiikawaStats({smallFeet: true, weapon: true, miniBag: true, calmAndReady: false, battlesWon: 0});
}
```
### Training.sol
1. Line-43 使用 `transferFrom()` 可能會將 NFT 轉入無法再轉出的合約地址,應該要使用 `safeTransferFrom()`
```solidity=
function unstake(uint256 tokenId) external {
require(stakes[tokenId].owner == msg.sender, "Not the token owner");
uint256 stakedDuration = block.timestamp - stakes[tokenId].startTime;
uint256 daysStaked = stakedDuration / 1 days;
// Assuming Conquest contract has a function to update metadata properties
IRequestQuest.ChiikawaStats memory stakedChiikawaStats = requestQuest.getChiikawaStats(tokenId);
emit Unstaked(msg.sender, tokenId, stakedDuration);
delete stakes[tokenId]; // Clear staking info
// Apply changes based on the days staked
if (daysStaked >= 1) {
stakedChiikawaStats.smallFeet = false;
CompensationContract.mint(msg.sender, 1);
}
if (daysStaked >= 2) {
stakedChiikawaStats.weapon = false;
CompensationContract.mint(msg.sender, 1);
}
if (daysStaked >= 3) {
stakedChiikawaStats.miniBag = false;
CompensationContract.mint(msg.sender, 1);
}
if (daysStaked >= 4) {
stakedChiikawaStats.calmAndReady = true;
CompensationContract.mint(msg.sender, 1);
}
// Only call the update function if the token was staked for at least one day
if (daysStaked >= 1) {
requestQuest.updateChiikawaStats(
tokenId,
stakedChiikawaStats.smallFeet,
stakedChiikawaStats.weapon,
stakedChiikawaStats.miniBag,
stakedChiikawaStats.calmAndReady,
stakedChiikawaStats.battlesWon
);
}
// Continue with unstaking logic (e.g., transferring the token back to the owner)
requestQuest.transferFrom(address(this), msg.sender, tokenId);
}
```
### Conquest.sol
1. `weedingOrBattle()` 可以被惡意合約調用,如果沒對戰成功獲得 CompensationToken [從Line-39] 就 revert transaction,造成遊戲公平性問題
2. Line-23和Line-41 `totalPrize` 這個變數沒實際功用,可以刪除來節省 gas
```solidity=
function weedingOrBattle(uint256 _tokenId, uint256 _compensationBet) external {
if (defender == address(0)) {
defender = msg.sender;
defenderBet = _compensationBet;
defenderTokenId = _tokenId;
emit Weeding(msg.sender, _tokenId, _compensationBet);
requestQuestNft.transferFrom(msg.sender, address(this), _tokenId);
CompensationToken.transferFrom(msg.sender, address(this), _compensationBet);
} else {
// CompensationToken.transferFrom(msg.sender, address(this), _compensationBet);
_battle(_tokenId, _compensationBet);
}
}
function _battle(uint256 _tokenId, uint256 _compensationBet) internal {
address _defender = defender;
require(defenderBet == _compensationBet, "Conquest: Bet amounts do not match");
uint256 defenderChiikawaSkill = getChiikawaSkill(defenderTokenId);
uint256 challengerChiikawaSkill = getChiikawaSkill(_tokenId);
uint256 totalBattleSkill = defenderChiikawaSkill + challengerChiikawaSkill;
uint256 totalPrize = defenderBet + _compensationBet;
uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))) % totalBattleSkill;
// Reset the defender
defender = address(0);
emit Battle(msg.sender, _tokenId, random < defenderChiikawaSkill ? _defender : msg.sender);
// If random <= defenderChiikawaSkill -> defenderChiikawaSkill wins, otherwise they lose
if (random <= defenderChiikawaSkill) {
// We give them the money the defender deposited, and the challenger's bet
CompensationToken.transfer(_defender, defenderBet);
CompensationToken.transferFrom(msg.sender, _defender, _compensationBet);
} else {
// Otherwise, since the challenger never sent us the money, we just give the money in the contract
CompensationToken.transfer(msg.sender, _compensationBet);
}
totalPrize = 0;
// Return the defender's NFT
requestQuestNft.transferFrom(address(this), _defender, defenderTokenId);
}
```