关于我第一个使用本地 RPC、Web3 和 Remix 的合同的一些问题

Some questions regarding my first contract using local RPC , Web3 and Remix

我是个新手,我为 POC 创建了我的第一个智能合约。 思路是模拟一个预订流程,客人先支付定金(unlockDoor方法),当他离开房间时,他会根据使用时间取回钱。

我将事件连接到我的 raspberry,以便打开相关房间的灯。

它适用于 javascript 虚拟机,但使用本地 RPC 我遇到了一些问题,我不明白为什么。

  1. 使用 html 页面内的简单按钮,unlockDoor 和 lockDoor 方法不会打开用于接受交易的元掩码弹出窗口。控制台内没有错误。
  2. 将 remix 与本地 RPC 结合使用:解锁门有效,锁门生成错误错误:执行事务时出现 VM 异常:gas 不足。 很多文章说要增加 gas 值,但它不起作用。可能我错过了什么。我不明白什么。 使用 javascript 虚拟机,所有方法都可以正常工作。
  3. 可能锁定方法中的双重传输使用 RPC(和测试网)产生了一些奇怪的东西。这些双重操作是否正确?我必须以其他方式管理它们吗?
  4. 基于第 2 点和第 3 点:对如何使用 "payable" 指令产生了混淆。

Index.htmljavascript

        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);

        });

合同。 注意:成本是每秒仅用于测试目的

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;

    }
}

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 自动注入自身并设置 web3。当您将 localhost 作为您的提供者传递时,您将覆盖 MM 并配置 web3 以直接与 TestRPC 对话。这是来自 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()

})

此外,您在调用 unlockDoor 时实际上并没有发送任何以太币。您需要在 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);
});

请注意,我也没有指定任何 gasLimitgasPrice,而您通常会指定。请参阅 web3js documentation 了解交易对象选项。

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.

gas 异常很难调试,这个有点奇怪。我认为您在估算气体时遇到了 TestRPC 的某种错误。该方法在 Remix VM 中工作正常,可以在通过连接到 TestRPC 的 MetaMask 时强制工作。

如果您通过 MetaMask 执行合约,您的 lockDoor 方法将显示为等待在 MetaMask 插件中批准的待处理交易。如果仔细观察,您会发现 gas limit 字段设置得非常低(此限制是根据 web3.eth.estimateGas 的结果确定的)。它实际上低于 21000 的最低金额,MetaMask 甚至会阻止您批准交易。但是,如果您查看 Remix 中的详细信息,gas 估计值大约是 MM 的 gas limit 字段中最初值的 2 倍。如果您手动更改 MM 中的 gas limit 值,交易将通过。 (请注意,我认为 运行 选项卡下的 Remix UI 中的 gas limit 字段在不使用 Remix VM 时会被忽略)。如果您直接连接到 TestRPC 并执行 gas 限制低于 21000 的方法,您将得到容易混淆的 "out of gas" 异常。通常,当通过你的客户端调用方法时,你会指定你自己的 gas 限制(请参阅我对上面 transactionObject 的评论)。

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?

你要小心如何从合同中转账。一般来说,您会希望遵循 withdrawal pattern。计算您要发送到地址的金额的逻辑应该与取款操作本身分开。使用 lockDoor() 确定欠 owner/renter 多少并将其存储在合同状态中。然后使用单独的 withdraw 函数来转移资金。

based on point 2 and 3: have generated confusion on how to use the "payable" instruction.

您只需要标记将要接收以太币的函数payable。从合约中发送以太币的函数不需要是 payable。此外,您可以通过添加 payable fallback function.

让合约接收以太币而不执行任何智能合约逻辑。