Reputation: 446
I'm newbie with solidity and I created my first smart contract for a POC. The idea is to simulate a reservation process where the guest pays an initial deposit (unlockDoor method) and, when he leaves the room, he will get money back based on the time of usage.
I connected events to my raspberry in order to turn on the lights of the related rooms.
It works with a javascript virtual machine but with a local RPC I have some issues and I do not understand why.
the javascript of Index.html
var web3 = new Web3(new
Web3.providers.HttpProvider("http://localhost:8545"));
web3.eth.defaultAccount = web3.eth.accounts[0];
var hotelReservation = web3.eth.contract(ABI);
var contract = hotelReservation.at(ADDRESS);
var room1_unlock = document.getElementById("room1");
room1_unlock.addEventListener("click", function(){
console.log("here");
contract.unlockDoor(1);
});
var room1_lock = document.getElementById("room1_lock");
room1_lock.addEventListener("click", function(){
console.log("here");
contract.lockDoor(1);
});
The contract. Note: cost is per second for testing pourpose only
contract HotelReservation{
//the owner of the contract
address owner;
//used for forcing the door lock
address raspberryAccount = XXXXXXXXX;
uint constant roomsNumber = 5;
//roomsNumber - sender
mapping (uint => address) reservations;
//address - deposit
mapping (address => uint) deposits;
//address - checkin timestamp
mapping (address => uint) checkins;
uint depositFee = 1 ether;
uint costPerSeconds = 0.0000115 ether;
event doorStatus (bool status, uint roomNr);
function HotelReservation (){
owner = msg.sender;
//init reservations
for (uint i=1; i <= roomsNumber; i++)
{
reservations[i] == 0;
}
}
modifier canReserveRoom(uint roomNr) {
bool canReserve = true;
if(roomNr <= 0 || roomNr > 5)
canReserve = false;
//check if sender has another camera reserved
for (uint i=1; i<= roomsNumber ; i++)
{
if (reservations[i] == msg.sender){
canReserve = false;
}
}
//camera is available
if(reservations[roomNr] != 0)
{
canReserve = false;
}
//money for deposit are enought
if(msg.value < depositFee)
{
canReserve = false;
}
require(canReserve);
_;
}
function unlockDoor(uint roomNr) canReserveRoom(roomNr) public payable returns (bool){
deposits[msg.sender] = depositFee;
reservations[roomNr] = msg.sender;
checkins[msg.sender] = block.timestamp;
doorStatus(true, roomNr);
return true;
}
modifier canLeaveRoom(uint roomNr) {
bool canLeave = true;
//no pending reservation
if (reservations[roomNr] != msg.sender){
canLeave = false;
}
require(canLeave);
_;
}
modifier isTheOwner(){
bool forceRoomLock = true;
if(msg.sender != raspberryAccount)
forceRoomLock = false;
require(forceRoomLock);
_;
}
function forceLockDoor(uint roomNr) isTheOwner public returns (bool){
address tenantAddress = reservations[roomNr];
//retrieve all deposit
owner.transfer(deposits[tenantAddress]);
reservations[roomNr] = 0;
deposits[tenantAddress] = 0;
checkins[tenantAddress] = 0;
doorStatus(false, roomNr);
return true;
}
function lockDoor(uint roomNr) canLeaveRoom(roomNr) public payable returns (bool){
//calculate the cost for the usage of the room
uint checkinTimestamp = checkins[msg.sender];
uint datetimeNow = block.timestamp;
uint usage = datetimeNow - checkinTimestamp;
uint usageInSeconds = uint8(usage % 60);
uint totalCost = usageInSeconds * costPerSeconds;
uint refound = deposits[msg.sender] - totalCost;
//send money back (deposit - usage)
msg.sender.transfer(refound);
//send money back to the hotel owner
owner.transfer(totalCost);
//clean information
reservations[roomNr] = 0;
deposits[msg.sender] = 0;
checkins[msg.sender] = 0;
doorStatus(false, roomNr);
return true;
}
}
Upvotes: 1
Views: 570
Reputation: 10991
Using simple buttons inside an html page, unlockDoor and lockDoor methods do not open the metamask popup for accepting the transaction. no errors inside the console.
MetaMask automagically injects itself and sets web3
. When you pass in localhost as your provider, you're overriding MM and configuring web3 to talk to TestRPC directly. This is the example code from their site:
window.addEventListener('load', function() {
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Use Mist/MetaMask's provider
window.web3 = new Web3(web3.currentProvider);
} else {
console.log('No web3? You should consider trying MetaMask!')
// fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
// Now you can start your app & access web3 freely:
startApp()
})
Also, you're not actually sending any ether when calling unlockDoor
. You need to specify the amount in a transactionObject
const transactionObj = {
from: accountAddress,
value: web3.toWei(amountInEther, 'ether'),
};
contract.unlockDoor(1, transactionObj, (error, result) => {
if (error)
console.log(error);
else
console.log(result);
});
Note that I haven't specified any gasLimit
or gasPrice
either, which you typically would. See the web3js documentation for transaction object options.
Using remix with local RPC: unlock door works, lock door generates error Error: VM Exception while executing transaction: out of gas. A lot of articles say to increase gas value but it does not work. Probably I missed something. I do not understand what. Using javascript virtual machine all methods work properly.
Out of gas exceptions can be difficult to debug and this one is a bit weird. I THINK you're hitting some sort of bug with TestRPC in estimating gas. The method works fine in the Remix VM and can be forced to work when going through MetaMask connected to TestRPC.
If you execute your contract through MetaMask, your lockDoor
method will show as a pending transaction waiting for approval in the MetaMask plugin. If you look carefully, you'll notice that the gas limit field is set pretty low (This limit is determined based on the result of web3.eth.estimateGas
). It's actually under the 21000 minimum and MetaMask will prevent you from even approving the transaction. However, if you look at the details in Remix, the gas estimate is about 2x the value initially in MM's gas limit field. If you manually change the gas limit value in MM, the transaction will go through. (Note, I think the gas limit field in the Remix UI under the Run tab is ignored when not using the Remix VM). If you connect directly to TestRPC and execute the method with the gas limit below 21000, you'll get the conveniently confusing "out of gas" exception. Usually, when calling methods through your client, you'd specify your own gas limit (see my comments on the transactionObject
above).
Probably the double transfer inside the lock method generates something strange using RPC (and test net). Are these double operations correct? Do I have to manage them in another way?
You want to be careful how you transfer money out of a contract. Generally speaking, you'll want to follow the withdrawal pattern. Logic that calculates how much you want to send to an address should be separated from the withdraw action itself. Use lockDoor()
to determine how much the owner/renter are owed and store that in the contract state. Then use a separate withdraw
function to transfer the funds.
based on point 2 and 3: have generated confusion on how to use the "payable" instruction.
You only need to mark functions payable
that are going to receive ether. Functions that send ether out from the contract don't need to be payable
. In addition, you can have a contract receive ether without executing any smart contract logic by adding a payable fallback function.
Upvotes: 1