1

我是新手,我为 POC 创建了我的第一个智能合约。这个想法是模拟一个预订过程,客人支付初始押金(unlockDoor 方法),当他离开房间时,他将根据使用时间获得退款。

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

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

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

Index.html 的 javascript

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

        });

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

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;

    }
}
4

1 回答 1

1

使用 html 页面中的简单按钮,unlockDoor 和 lockDoor 方法不会打开元掩码弹出窗口以接受交易。控制台内没有错误。

MetaMask 自动注入并设置web3. 当您将 localhost 作为您的提供者传递时,您将覆盖 MM 并将 web3 配置为直接与 TestRPC 对话。这是他们网站上的示例代码:

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

请注意,我没有指定任何gasLimit一个gasPrice,您通常会这样做。有关事务对象选项,请参阅web3js 文档

将 remix 与本地 RPC 一起使用:解锁门有效,锁门生成错误错误:执行事务时 VM 异常:气体不足。很多文章说要增加gas值,但它不起作用。可能我错过了什么。我不明白什么。使用 javascript 虚拟机所有方法都可以正常工作。

Out of 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 VM 时,Remix UI 中 Run 选项卡下的 gas limit 字段会被忽略)。如果您直接连接到 TestRPC 并在 gas 限制低于 21000 的情况下执行该方法,您将得到方便混淆的“out of gas”异常。通常,当通过您的客户端调用方法时,您会指定自己的 gas 限制(请参阅我在transactionObject上面的评论)。

使用 RPC(和测试网),lock 方法中的双重传输可能会产生一些奇怪的东西。这些双重操作正确吗?我必须以其他方式管理它们吗?

您要小心如何将资金从合同中转出。一般来说,你会想要遵循退出模式。计算您要发送到地址的金额的逻辑应该与提款操作本身分开。用于lockDoor()确定欠所有者/承租人的金额并将其存储在合同状态中。然后使用单独的withdraw功能转移资金。

基于第 2 点和第 3 点:对如何使用“应付”指令产生了混淆。

您只需要标记payable将要接收以太币的函数。从合约中发送以太币的函数不需要是payable. 此外,您可以通过添加应付回退功能让合约接收以太币而无需执行任何智能合约逻辑。

于 2018-01-03T19:57:41.360 回答