javascript - 关于我第一个使用本地 RPC、Web3 和 Remix 的契约(Contract)的一些问题

标签 javascript solidity smartcontracts web3js remix

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

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

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

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

        });

契约(Contract)。 注意:成本是每秒,仅用于测试目的

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 自动注入(inject)自身并设置 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 执行您的契约(Contract),您的 lockDoor 方法将显示为待处理交易,等待 MetaMask 插件中的批准。如果仔细观察,您会注意到 gas limit 字段设置得非常低(此限制是根据 web3.eth.estimateGas 的结果确定的)。它实际上低于 21000 的最低金额,MetaMask 甚至会阻止您批准交易。但是,如果您查看 Remix 中的详细信息,gas 估计值大约是 MM 的 gas limit 字段中最初值的 2 倍。如果您手动更改 MM 中的 gas limit 值,交易将通过。 (请注意,我认为当不使用 Remix VM 时,Remix UI 中“运行”选项卡下的气体限制字段会被忽略)。如果您直接连接到 TestRPC 并在 gas 限制低于 21000 的情况下执行该方法,您将得到容易混淆的“out of 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?

您要小心如何从契约(Contract)中转账。一般来说,您需要关注 withdrawal pattern。 .计算您要发送到地址的金额的逻辑应该与取款操作本身分开。使用 lockDoor() 确定所有者/承租人欠多少钱并将其存储在契约(Contract)状态中。然后使用单独的 withdraw 函数来转移资金。

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

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

关于javascript - 关于我第一个使用本地 RPC、Web3 和 Remix 的契约(Contract)的一些问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48074743/

相关文章:

javascript - 如何合并维数组

javascript - JavaScript 中的 3 级引号

blockchain - 如何使用 Solidity 和 Web.js 在以太坊区 block 链上保存和检索数据

swap - PancakeSwap 合约/将代币换成另一个代币

ethereum - 如何在 Solidity 中声明常量

javascript - 在 bootstrap-fileinput 中继续 filepredelete 事件之前等待 Sweetalert2 异步确认

javascript - SailsJs - 将 html View 导出为 PDF

ethereum - 如何在代币合约中区分在 uniswap 上买卖 erc-20 代币

blockchain - 无法调用智能合约中的函数

rust - NEAR 协议(protocol) Rust 合约——near-sdk-rs 数据结构和 std 有什么区别?