문제 개요

채굴하여 1,000,000DC를 벌어야 한다. 250번의 횟수 제한이 존재한다.
핵심 코드
app.post('/api/dig', requireAuth, requireStarted, (req, res) => {
const user = req.user;
const { direction } = req.body;
if (!['up', 'down', 'left', 'right'].includes(direction)) {
return res.status(400).json({ error: 'invalid direction' });
}
const pickaxe = PICKAXES[user.pickaxe];
if (user.energy < 1) {
return res.status(400).json({ error: 'no energy left' });
}
let dx = 0, dy = 0;
if (direction === 'left') dx = -1;
if (direction === 'right') dx = 1;
if (direction === 'down') dy = -1;
if (direction === 'up') dy = 1;
let mined = {};
let earnings = 0;
let cx = user.x;
let cy = user.y;
let remaining = pickaxe.range;
while (remaining > 0) {
cx += dx;
cy += dy;
if (cy > 0) break;
const key = cx + ',' + cy;
if (user.mined[key]) {
remaining--;
continue;
}
const blockType = getBlockType(cx, cy);
const ore = ORES[blockType];
if (!ore) break;
if (ore.tier > pickaxe.tier) break;
mined[key] = true;
earnings += ore.reward;
remaining--;
}
const blocks = Object.keys(mined);
if (blocks.length === 0) {
return res.status(400).json({ error: 'nothing to mine' });
}
let haulBase = 0;
for (const key of blocks) {
const [bx, by] = key.split(',').map(Number);
const ore = ORES[getBlockType(bx, by)];
haulBase += ore.reward;
}
const cost = Math.floor(haulBase * HAULING_RATE);
const net = earnings - cost;
user.energy -= 1;
for (const key of blocks) {
user.mined[key] = true;
}
user.balance += net;
const entry = {
time: Date.now(),
direction,
blocks: blocks.length,
earnings,
cost,
net,
position: { x: user.x, y: user.y },
};
user.log.push(entry);
if (user.log.length > 50) user.log.shift();
res.json({
success: true,
blocks: blocks.length,
earnings,
cost,
net,
balance: user.balance,
energy: user.energy,
position: { x: user.x, y: user.y },
});
});
풀이
좌표값에 대한 검증이 없다. Javascript에서 사용하는 모든 수는 64비트 부동 소수점(IEEE 754) 형식으로 이루어져 있다. 따라서 2^53-1(9007199254740991)을 넘어가면 정밀도 손실이 발생한다.
실제로 javascript에서 x = 9007199254740992 라는 값을 입력한 뒤 x+1을 수행하면 9007199254740992라는 결과가 출력된다.

그래서 방향이 right일 때, cx += dx 에서 cx의 값이 변하지 않는 버그가 발생한다. 그래서 만약 9007199254740991(2에서는 오른쪽으로 캘 수가 없다)에서 오른쪽으로 채굴을 시도하면 x=9007199254740992 좌표에서 반복적으로 채굴을 시도하게 된다.
(거기다 mined[key] = true; 부분때문에 user.mined의 갱신이 전체 채굴 후에 이루어진다.)
그래서 아래와 같이 채굴을 진행하면

금 타일 40회만큼의 채굴을 진행하면

실제로 750(금)*39(범위40)에 해당하는 29288만큼 포인트가 증가하는 것을 확인할 수 있었다.

