Как вы можете справиться с ожидаемым броском в контрактном тесте с использованием трюфелей и ethereum-testRPC?

Можно ли написать тест с использованием трюфеля, который пытается подтвердить, что бросок происходит в контракте? Например, если у меня был контракт с функцией ...

contract TestContract {
  function testThrow() {
    throw;
  }
}

, если я пишу тест в трюфеле, который вызывает эту функцию, тогда тест трюфеля в основном сбой:

Error: VM Exception while executing transaction: invalid JUMP

Есть ли способ обработать это исключение из вашего теста, чтобы убедиться, что это действительно произошло? Причина, по которой я хочу сделать это, - проверить, действительно ли мои функции бросают, когда пользователь передает недопустимый ввод?

23 голоса | спросил thedob 7 +03002016-10-07T19:15:15+03:00312016bEurope/MoscowFri, 07 Oct 2016 19:15:15 +0300 2016, 19:15:15

7 ответов


10

Вы можете использовать команду OpenZeppelin expectThrow -

Источник: https://github.com/OpenZeppelin /дирижабль-монолитность /BLOB /Master /тест /хелперы /expectThrow.js

export default async promise => {
      try {
        await promise;
      } catch (error) {
        // TODO: Check jump destination to destinguish between a throw
        //       and an actual invalid jump.
        const invalidJump = error.message.search('invalid JUMP') >= 0;
        // TODO: When we contract A calls contract B, and B throws, instead
        //       of an 'invalid jump', we get an 'out of gas' error. How do
        //       we distinguish this from an actual out of gas event? (The
        //       testrpc log actually show an 'invalid jump' event.)
        const outOfGas = error.message.search('out of gas') >= 0;
        assert(
          invalidJump || outOfGas,
          "Expected throw, got '" + error + "' instead",
        );
        return;
      }
      assert.fail('Expected throw not received');
    };

Я использую его в своих тестовых случаях, например:

import expectThrow from './helpers/expectThrow';
.
.
.
describe('borrowBook', function() {
        it("should not allow borrowing book if value send is less than 100", async function() {
            await lms.addBook('a', 'b', 'c', 'e', 'f', 'g');
            await lms.addMember('Michael Scofield', accounts[2], "[email protected]");
            await lms.borrowBook(1, {from: accounts[2], value: 10**12})
            await expectThrow(lms.borrowBook(1, {from: accounts[2], value: 10000})); // should throw exception
        });
});
ответил Sanchit 13 Maypm17 2017, 19:05:09
8

Вот шаблон, который я использую в настоящее время для проверки ожидаемых throws (например, при недопустимом вводе). Solidity реализует throw с помощью JUMPing на недопустимый пункт назначения, поэтому мы улавливаем ошибку, а затем ищем строку «invalid JUMP» в сообщении об ошибке ... Я бы предпочел бы более надежный способ, но haven Еще ничего не найдено.

var EthWall = artifacts.require("./EthWall.sol");

contract('TestContract', function(accounts) {
  it("should throw an exception", function() {
    return EthWall.deployed().then(function(instance) {
      return instance.testThrow.call();
    }).then(function(returnValue) {
      assert(false, "testThrow was supposed to throw but didn't.");
    }).catch(function(error) {
      if(error.toString().indexOf("invalid JUMP") != -1) {
        console.log("We were expecting a Solidity throw (aka an invalid JUMP), we got one. Test succeeded.");
      } else {
        // if the error is something else (e.g., the assert from previous promise), then we fail the test
        assert(false, error.toString());
      }
    });
  });
});
ответил Gabriel Parent 6 MaramMon, 06 Mar 2017 11:03:56 +03002017-03-06T11:03:56+03:0011 2017, 11:03:56
8

Другие ответы в этом потоке кажутся действительными, однако я считаю, что этот код более краткий и читаемый.

Это работает с solidity 0.4.12-develop

it("should throw if the car is not blue", function() {
    return CarFactory.deployed()
        .then(function(factory) {
            return factory.createCar("red");
         })
         .then(assert.fail)
         .catch(function(error) {
                assert.include(
                    error.message,
                    'out of gas',
                    'red cars should throw an out of gas exception.'
                )
         });
});

Я заметил, что при использовании трюфеля + testrpc некоторые throws вызывают исключение «из газа», а другие - исключение «неверный код операции». Я не подтвердил причины этих разных сообщений, но они кажутся последовательными. Я рекомендую наивно тестировать оба исключения, поскольку это потенциально полезная информация, если сообщение об исключении изменяется.

ответил paulhauner 11 J0000006Europe/Moscow 2017, 15:49:44
2

Вы можете использовать этот текст, который я создал :

var expectedExceptionPromise = function (action, gasToUse) {
  return new Promise (функция (разрешить, отклонить) {
      пытаться {
        Решимость (действие ());
      } catch (e) {
        отвергнуть (е);
      }
    })
    .then (функция (txn) {
      //https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6
      return web3.eth.getTransactionReceiptMined (txn);
    })
    .then (функция (получение) {
      //Мы в Гете
      assert.equal (receipt.gasUsed, gasToUse, «должен был использовать весь газ»);
    })
    .catch (функция (e) {
      if ((e + "") .indexOf ("invalid JUMP") || (e + "") .indexOf ("out of gas")> -1) {
        //Мы находимся в TestRPC
      } else if ((e + "") .indexOf («проверьте количество газа»)> -1) {
        //Мы находимся в Geth для развертывания
      } else {
        бросать e;
      }
    });
};
ответил Xavier Leprêtre B9lab 7 +03002016-10-07T19:30:54+03:00312016bEurope/MoscowFri, 07 Oct 2016 19:30:54 +0300 2016, 19:30:54
2

Другие ответы не будут работать для более новых версий Solidity (0.4.10 и выше, я считаю).

Вместо этого я использую аналогичный шаблон, но с двумя сравнениями строк, чтобы поймать новое сообщение об ошибке, а также более старое (только для устаревших контрактов /тестов).

function assertThrows (fn, args) {
  //Asserts that `fn(args)` will throw a specific type of error.
  return new Promise(
    function(resolve, reject){
      fn.apply(this, args)
      .then(() => {
        assert(false, 'No error thrown.');
        resolve();
      },
      (error) => {
        var errstr = error.toString();
        var newErrMsg = errstr.indexOf('invalid opcode') != -1;
        var oldErrMsg = errstr.indexOf('invalid JUMP') != -1;
        if(!newErrMsg && !oldErrMsg)
          assert(false, 'Did not receive expected error message');
        resolve();
      })
  })
}

Релевантная проблема в Solidity GH

ответил Travis Jacobs 13 Mayam17 2017, 02:38:09
0

OpenZeppelin имеет вспомогательный помощник expectThrow, который полезен для этого. Он находится в test/helpers/expectThrow.js

module.exports = async promise => {
  try {
    await promise;
  } catch (error) {
    // TODO: Check jump destination to destinguish between a throw
    //       and an actual invalid jump.
    const invalidOpcode = error.message.search('invalid opcode') >= 0;
    // TODO: When we contract A calls contract B, and B throws, instead
    //       of an 'invalid jump', we get an 'out of gas' error. How do
    //       we distinguish this from an actual out of gas event? (The
    //       testrpc log actually show an 'invalid jump' event.)
    const outOfGas = error.message.search('out of gas') >= 0;
    assert(
      invalidOpcode || outOfGas,
      "Expected throw, got '" + error + "' instead",
    );
    return;
  }
  assert.fail('Expected throw not received');
};

пример использования находится в test/MintableToken.js, например:

import expectThrow from './helpers/expectThrow';
...

await expectThrow(token.mint(accounts[0], 100));
...
ответил sofend 6 12017vEurope/Moscow11bEurope/MoscowMon, 06 Nov 2017 08:05:04 +0300 2017, 08:05:04
0

Вот еще один подход (основанный на решениях выше).

При определении настраиваемых функций ожидания, подобных этим (не стесняйтесь добавлять больше), я чувствую, что тесты более четко выражают то, что вы ожидаете.

// expectThrow.js

const expectThrow = (text) => async (promise) => {
   try {
     await promise;
   } catch (error) {
     assert(error.message.search(text) >= 0, "Expected throw, got '" + error + "' instead")
     return
   }
   assert.fail('Expected throw not received')
 }

 module.exports =  {
   expectOutOfGas: expectThrow('out of gas'),
   expectRevert: expectThrow('revert'),
   expectInvalidJump: expectThrow('invalid JUMP')
 }

Тогда, например, в вашем тестировании выполните следующие действия:

/// test.js

const { expectRevert } from './expectThrow.js'

it('your test name', async () => {
  await expectRevert(
    // your contract call
  )
})
ответил ClementBresson 6 PM00000060000003431 2018, 18:38:34

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132