使用本地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:
- Incorrect constructor initialization: You used
reservations[i] == 0(comparison operator) instead ofreservations[i] = 0(assignment). This means your room reservation state wasn't set up properly, leading to unexpected behavior in modifiers and calculations. - Broken cost calculation: Your
usageInSecondswas calculating the remainder of seconds (e.g., a 120-second stay would show 0 seconds used) and casting touint8(risky for overflow). This led to invalidtotalCostvalues 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:
transfersends 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
payableonly to functions that accept ETH:unlockDoorneedspayablebecause guests send the deposit when unlocking the door.lockDoorandforceLockDoorshould NOT havepayable—they don't accept ETH; they process refunds and fee transfers.
- When calling a payable function from the frontend, you must specify the
valueparameter (as shown in the frontend fix) to send the required ETH amount.
Bonus: Additional Improvements
- Use
uint256instead ofuintfor clarity (thoughuintis an alias foruint256). - 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
canReserveRoommodifier 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

