You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用本地RPC、Web3和Remix开发Solidity合约遇多类问题求助

Hey there! Let's break down your issues one by one and fix them step by step—you're already doing great building a POC with Solidity + Raspberry Pi, that's a really cool project!


1. Frontend Doesn't Trigger MetaMask Transaction Popup

Root Cause

Your frontend code is creating a direct HttpProvider connection to your local RPC node instead of using MetaMask's injected provider. MetaMask only intercepts transactions when you use its own provider instance, not a standalone HttpProvider. Also, you weren't passing the required deposit amount when calling unlockDoor—that would have failed the modifier check silently too!

Fix

Update your JavaScript code to use MetaMask's provider and properly handle transaction parameters:

// Check if MetaMask is installed
if (typeof window.ethereum !== 'undefined') {
  window.web3 = new Web3(window.ethereum);
  // Request account access from MetaMask
  window.ethereum.request({ method: 'eth_requestAccounts' })
    .then(accounts => {
      web3.eth.defaultAccount = accounts[0];
    })
    .catch(err => console.error("MetaMask access denied:", err));
} else {
  alert('Please install MetaMask to interact with the contract!');
}

// Initialize contract after MetaMask is ready
let contract;

async function initContract() {
  const hotelReservation = web3.eth.contract(ABI);
  contract = hotelReservation.at(ADDRESS);
  
  // Unlock button handler
  const room1_unlock = document.getElementById("room1");
  room1_unlock.addEventListener("click", async function(){
    console.log("Unlocking room 1...");
    try {
      // Estimate gas and send transaction with deposit
      const gas = await contract.unlockDoor.estimateGas(1, { value: web3.toWei(1, 'ether') });
      await contract.unlockDoor(1, { 
        value: web3.toWei(1, 'ether'), // Send 1 ether deposit
        gas: gas + 10000 // Add buffer to avoid gas issues
      });
    } catch (err) {
      console.error("Error unlocking door:", err);
    }
  });

  // Lock button handler
  const room1_lock = document.getElementById("room1_lock");
  room1_lock.addEventListener("click", async function(){
    console.log("Locking room 1...");
    try {
      const gas = await contract.lockDoor.estimateGas(1);
      await contract.lockDoor(1, { gas: gas + 10000 });
    } catch (err) {
      console.error("Error locking door:", err);
    }
  });
}

// Initialize contract on page load
window.onload = initContract;

2. Remix + Local RPC: lockDoor Throws "Out of Gas" Error

Root Causes

There are two critical bugs in your Solidity code causing this:

  1. Incorrect constructor initialization: You used reservations[i] == 0 (comparison operator) instead of reservations[i] = 0 (assignment). This means your room reservation state wasn't set up properly, leading to unexpected behavior in modifiers and calculations.
  2. Broken cost calculation: Your usageInSeconds was calculating the remainder of seconds (e.g., a 120-second stay would show 0 seconds used) and casting to uint8 (risky for overflow). This led to invalid totalCost values that could break transfer logic and cause gas issues.

Fixes

First, fix the constructor initialization:

function HotelReservation (){
  owner = msg.sender;
  // Initialize booking info (use = instead of ==)
  for (uint i=1; i <= roomsNumber; i++) {
    reservations[i] = 0;
  }
}

Then, fix the cost calculation in lockDoor—since block.timestamp is in seconds, the difference is already total seconds used:

function lockDoor(uint roomNr) canLeaveRoom(roomNr) public returns (bool){
  // Calculate room usage cost
  uint checkinTimestamp = checkins[msg.sender];
  uint datetimeNow = block.timestamp;
  uint totalSecondsUsed = datetimeNow - checkinTimestamp;
  uint totalCost = totalSecondsUsed * costPerSeconds;
  uint refund = deposits[msg.sender] - totalCost;

  // Add safety check to prevent negative refunds
  require(totalCost <= deposits[msg.sender], "Total cost exceeds deposit");
  
  // Refund deposit minus cost
  msg.sender.transfer(refund);
  // Transfer fees to hotel owner
  owner.transfer(totalCost);
  
  // Clear booking info
  reservations[roomNr] = 0;
  deposits[msg.sender] = 0;
  checkins[msg.sender] = 0;
  doorStatus(false, roomNr);
  return true;
}

Also, remove the payable modifier from lockDoor—this function doesn't accept ETH, so adding payable is unnecessary and can lead to confusion.


3. transfer Operations & payable Usage Clarification

Are the transfer Operations Correct?

Your transfer calls are technically valid for basic use cases, but keep these best practices in mind:

  • transfer sends exactly 2300 gas, which works for EOA (external account) recipients, but will fail if the recipient is a contract that needs more gas to handle the transfer (e.g., a contract with a fallback function that does work). For your hotel POC, this is fine since guests and owners are likely regular accounts.
  • Always add a safety check like require(totalCost <= deposits[msg.sender]) to avoid negative refunds (we added this in the fix above).

payable Usage Explained

  • Add payable only to functions that accept ETH:
    • unlockDoor needs payable because guests send the deposit when unlocking the door.
    • lockDoor and forceLockDoor should NOT have payable—they don't accept ETH; they process refunds and fee transfers.
  • When calling a payable function from the frontend, you must specify the value parameter (as shown in the frontend fix) to send the required ETH amount.

Bonus: Additional Improvements

  • Use uint256 instead of uint for clarity (though uint is an alias for uint256).
  • Add a fallback function to reject accidental ETH transfers to the contract:
fallback() external payable {
  revert("Do not send ETH directly to this contract");
}
  • Simplify the canReserveRoom modifier for readability:
modifier canReserveRoom(uint roomNr) {
  require(roomNr >= 1 && roomNr <= roomsNumber, "Invalid room number");
  require(reservations[roomNr] == address(0), "Room is already reserved");
  require(msg.value == depositFee, "Incorrect deposit amount");
  
  // Check if user already has a reservation
  bool hasExistingReservation = false;
  for (uint i=1; i<= roomsNumber ; i++) {
    if (reservations[i] == msg.sender){
      hasExistingReservation = true;
      break;
    }
  }
  require(!hasExistingReservation, "You already have a reserved room");
  
  _;
}

内容的提问来源于stack exchange,提问作者MetalMad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:15:41