How do you test a Require statement in a smartcontract using Mocha? - testing

I am fairly new to developing smartcontracts and have run into an issue while testing. My intention is to ensure the smartcontract cannot mint more than 13 ERC721 tokens. My understanding is that a require function can have a second string argument that will revert a string as an error if the require condition is not met. Is this correct?
The following is my smart contract code:
contract TheCondemned_Episode is ERC721Enumerable {
string[] public episodes;
constructor() ERC721("TheCondemned_e1", "TCe1") public {
}
function mint(string memory _episode) public {
require(episodes.length <= 13, "Cannot make more than 13 episodes");
episodes.push(_episode);
uint _id= episodes.length;
_mint(msg.sender, _id);
}
}
And the test I am running is as follows:
it('Cannot create more than 13 episodes', async() => {
for(var i=0; i===13; i++){
var episode= `Episode ${i}`
await contract.mint(episode)
}
try {
await contract.mint('Episode 14');
assert(true);
}
catch (err) {
return;
}
assert(false, "The contract did not throw.");
The test fails and returns "The contract did not throw". What is the best practice in regards to catching a revert string from a failed require condition when testing?

My understanding is that a require function can have a second string argument that will revert a string as an error if the require condition is not met. Is this correct?
That's correct. Here's an example of an always failing require() condition that throws an exception with the error message.
require(false, 'Error message');
However, you have a logical error in the Solidity require() condition, as well as in the JS test snippet.
First, let's uncover the Solidity code. For simplicity, let's assume you're allowing to mint only 1 episode.
require(episodes.length <= 1, "Cannot make more than 1 episode");
First iteration (expected to pass)
episodes.length is 0, that's <= 1. Condition passes, you mint the first token, and then push to the episodes array, so its length becomes 1 after the condition.
Second iteration (expected to fail)
episodes.length is 1, that's still <= 1. So the condition passes as well.
Solution: Replace the <= (less than or equal) to just < (less than).
require(episodes.length < 1, "Cannot make more than 1 episode");
First iteration (expected to pass)
episodes.length is 0, that's < 1. Condition passes, you mint the first token, and then push to the episodes array, so its length becomes 1 after the condition.
Second iteration (expected to fail)
episodes.length is 1, which fails the condition 1 < 1, as you expect.
I'm assuming that your intention in the JS snippet is to call the mint() function 13 times in the loop, and then 14th time in the try/catch block.
However, the loop currently doesn't perform any iteration. So in fact, you're only executing the mint() function once (in the try/catch block).
The second parameter in the for loop is a condition stating "this loop will keep iterating for as long as this condition is met". But since you set the value of i to 0 in the first parameter, the loop condition (i===13) is not met, and the loop doesn't perform even the first iteration.
Solution: Check whether the i is "less than 13" instead of just "equals 13".
for(var i = 0; i < 13; i++) {
This way, the loop will iterate 13 times.

Related

First function call success, second call fails. (Solidity with Hardhat)

I've been trying to store 10000 16byte strings (the string that I used is 'abcdefghijklmnop') in a solidity mapping: mapping(uint256 => string)
I've tried sending all 10000 strings in one transaction and it always exceeds the maximum gas limit and fails.
Divided those 10000 strings into equal 10 arrays and tried to do 10 transactions.. The first transaction, with the first 1000 strings happens successfully but the following transactions fail due to a gas issue
Solidity Code:
uint256 public totalGenomes;
mapping(uint256 => string) public genomesList;
function addGenome(string[] memory genome) public {
for (uint256 index = totalGenomes; index < (1000 + totalGenomes); index++) {
genomesList[index] = genome[index];
}
totalGenomes += 1000;
}
Hardhat script: there are 10 files genomes0.txt, genomes1.txt, genomes2.txt .... each containing 1000 lines of 'abcdefghijklmnop'
for (i = 0; i < 10; i++) {
let filepath = `./img/genomes${i}.txt`;
let genomes1000 = [];
genomes1000 = fs.readFileSync(filepath).toString().split('\n');
console.log(filepath);
await svgNFT.addGenome(genomes1000);
}
Error:
Error: cannot estimate gas; transaction may fail or may require manual gas limit
I also intend to minimise the gas price for the whole process.
Have anyone here got prior experience on such a scenario?
UPDATE: I got past the above problem.
I did this suggested by Miguel_LZPF on Hardhat discord:
contract.function(param1, param2..., lastfunctionparam, {gasLimit: xxxxx})
and define the gasLimit and gasPrice manually. And now, I'm stuck in another problem.
https://rinkeby.etherscan.io/address/0x11740C2367a0F0465d31b3612B3A5464dC7c8Afb
Warning! Error encountered during contract execution [execution
reverted]
Still the first transaction in the loop happens to be successful the rest fails.

return#forEach does not seem to exit forEach{}

The count is 4 at the end of the code below. I expected 0. Why is it 4? How can I get 0?
var count = 0;
"hello".forEach {
if(it == 'h')
{
println("Exiting the forEach loop. Count is $count");
return#forEach;
}
count++;
}
println("count is $count");
Output:
Exiting the forEach loop. Count is 0
count is 4
return#forEach does not exit forEach() itself, but the lambda passed to it ("body" of forEach()). Note that this lambda is executed several times - once per each item. By returning from it you actually skip only a single item, so this is similar to continue, not to break.
To workaround this you can create a label in the outer scope and return to it:
var count = 0;
run loop# {
"hello".forEach {
if(it == 'h')
{
println("Exiting the forEach loop. Count is $count");
return#loop;
}
count++;
}
}
This is described here: https://kotlinlang.org/docs/returns.html#return-at-labels
Note that the use of local returns in previous three examples is similar to the use of continue in regular loops. There is no direct equivalent for break, but it can be simulated by adding another nesting lambda and non-locally returning from it
It is 4 because the forEach call the lambda passed to it for each character in the string, so the return#forEach in your code return for the first element. You can use a for loop and use break to obtain 0.
return#forEach returns from the lambda function. But the forEach function is a higher-order function that calls the lambda repeatedly for each item in the iterator. So when you return from the lambda, you are only returning for that single item in the iterator. It is analogous to using continue in a traditional for loop.
If you want to exit iteration in a higher-order function completely, you have to use labels. And as I type this, I see another answer already shows how to do that, but I'll leave this in case the different explanation helps.
If your objective is to count the number of characters before 'h', you could do something like this:
val numCharsBeforeH = "hello".takeWhile { it != 'h' }.length
From your comment to Tenfour04's answer:
This is not very convenient. Why didn't the makers of Kotlin create a "break" equivalent?
Here is a quote of the "Loops" section of the Coding conventions:
Prefer using higher-order functions (filter, map etc.) to loops.
Exception: forEach (prefer using a regular for loop instead, unless
the receiver of forEach is nullable or forEach is used as part of a
longer call chain).
When making a choice between a complex expression using multiple
higher-order functions and a loop, understand the cost of the
operations being performed in each case and keep performance
considerations in mind.
Indeed, using a regular for loop with break does what you expect:
var count = 0;
for (char in "hello") {
if (char == 'h') {
println("Breaking the loop. Count is $count")
break
}
count++
}
println("count is $count")
Output:
Breaking the loop. Count is 0
count is 0
Except for very simple operations, there are probably better ways to do what you need than using forEach.

Counter as variable in for-in-loops

When normally using a for-in-loop, the counter (in this case number) is a constant in each iteration:
for number in 1...10 {
// do something
}
This means I cannot change number in the loop:
for number in 1...10 {
if number == 5 {
++number
}
}
// doesn't compile, since the prefix operator '++' can't be performed on the constant 'number'
Is there a way to declare number as a variable, without declaring it before the loop, or using a normal for-loop (with initialization, condition and increment)?
To understand why i can’t be mutable involves knowing what for…in is shorthand for. for i in 0..<10 is expanded by the compiler to the following:
var g = (0..<10).generate()
while let i = g.next() {
// use i
}
Every time around the loop, i is a freshly declared variable, the value of unwrapping the next result from calling next on the generator.
Now, that while can be written like this:
while var i = g.next() {
// here you _can_ increment i:
if i == 5 { ++i }
}
but of course, it wouldn’t help – g.next() is still going to generate a 5 next time around the loop. The increment in the body was pointless.
Presumably for this reason, for…in doesn’t support the same var syntax for declaring it’s loop counter – it would be very confusing if you didn’t realize how it worked.
(unlike with where, where you can see what is going on – the var functionality is occasionally useful, similarly to how func f(var i) can be).
If what you want is to skip certain iterations of the loop, your better bet (without resorting to C-style for or while) is to use a generator that skips the relevant values:
// iterate over every other integer
for i in 0.stride(to: 10, by: 2) { print(i) }
// skip a specific number
for i in (0..<10).filter({ $0 != 5 }) { print(i) }
let a = ["one","two","three","four"]
// ok so this one’s a bit convoluted...
let everyOther = a.enumerate().filter { $0.0 % 2 == 0 }.map { $0.1 }.lazy
for s in everyOther {
print(s)
}
The answer is "no", and that's a good thing. Otherwise, a grossly confusing behavior like this would be possible:
for number in 1...10 {
if number == 5 {
// This does not work
number = 5000
}
println(number)
}
Imagine the confusion of someone looking at the number 5000 in the output of a loop that is supposedly bound to a range of 1 though 10, inclusive.
Moreover, what would Swift pick as the next value of 5000? Should it stop? Should it continue to the next number in the range before the assignment? Should it throw an exception on out-of-range assignment? All three choices have some validity to them, so there is no clear winner.
To avoid situations like that, Swift designers made loop variables in range loops immutable.
Update Swift 5
for var i in 0...10 {
print(i)
i+=1
}

Is it valid to rebind a variable in a while loop?

Is it valid to rebind a mutable variable in a while loop? I am having trouble getting the following trivial parser code to work. My intention is to replace the newslice binding with a progressively shorter slice as I copy characters out of the front of the array.
/// Test if a char is an ASCII digit
fn is_digit(c:u8) -> bool {
match c {
30|31|32|33|34|35|36|37|38|39 => true,
_ => false
}
}
/// Parse an integer from the front of an ascii string,
/// and return it along with the remainder of the string
fn parse_int(s:&[u8]) -> (u32, &[u8]) {
use std::str;
assert!(s.len()>0);
let mut newslice = s; // bytecopy of the fat pointer?
let mut n:Vec<u8> = vec![];
// Pull the leading digits into a separate array
while newslice.len()>0 && is_digit(newslice[0])
{
n.push(newslice[0]);
newslice = newslice.slice(1,newslice.len()-1);
//newslice = newslice[1..];
}
match from_str::<u32>(str::from_utf8(newslice).unwrap()) {
Some(i) => (i,newslice),
None => panic!("Could not convert string to int. Corrupted pgm file?"),
}
}
fn main(){
let s:&[u8] = b"12345";
assert!(s.len()==5);
let (i,newslice) = parse_int(s);
assert!(i==12345);
println!("length of returned slice: {}",newslice.len());
assert!(newslice.len()==0);
}
parse_int is failing to return a slice that is smaller than the one I passed in:
length of returned slice: 5
task '<main>' panicked at 'assertion failed: newslice.len() == 0', <anon>:37
playpen: application terminated with error code 101
Run this code in the rust playpen
As Chris Morgan mentioned, your call to slice passes the wrong value for the end parameter. newslice.slice_from(1) yields the correct slice.
is_digit tests for the wrong byte values. You meant to write 0x30, etc. instead of 30.
You call str::from_utf8 on the wrong value. You meant to call it on n.as_slice() rather than newslice.
Rebinding variables like that is perfectly fine. The general rule is simple: if the compiler doesn’t complain, it’s OK.
It’s a very simple error that you’ve made: your slice end point is incorrect.
slice produces the interval [start, end)—a half-open range, not closed. Therefore when you wish to just remove the first character, you should be writing newslice.slice(1, newslice.len()), not newslice.slice(1, newslice.len() - 1). You could also write newslice.slice_from(1).

Why is java program skipping my inner loop?

Hello I'm a beginning programming Student and I am practicing using loops to validate input. Unfortunately, the loop works but skips the inner loop entirely... I get now error message or prompt...
Here is my code: [I BORROWED IT FROM AN ANSWER ON THIS SITE ABOUT VALIDATING INPUT SO I COULD TEST IT.]
import java.util.Scanner;
public class ValidationTest
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int number;
do {
System.out.println("Please enter a positive number!");
while (!sc.hasNextInt())
{
System.out.println("That's not a number!");
sc.next(); // this is important!
}
number = sc.nextInt();
} while (number <= 0);
System.out.println("Thank you! Got " + number);
}
}
The inner loop :
while (!sc.hasNextInt())
{
System.out.println("That's not a number!");
sc.next(); // this is important!
}
number = sc.nextInt();
only check if your input is not a number, if you input -123, the function !sc.hasNextInt() is false so it'll skip the loop, if you want to check the number is negative, add this check after the assign number value:
if(number <= 0 ){
System.out.println("The number is negative!");
}
You don't have to make another loop for checking the number is negative or not because of the first loop had it, and do...while loop will make sure you have to run the loop at least one time
The while loop will be skipped if the condition (in parentheses after while) is false when the while loop is first executed.
You can use a debugger to step through the code to see what is going on.
If you do not know how to use a debugger, stop everything you are doing and do some Google searches or whatever you need to do to find out how to use one.
your int number is NaN (not a number). Try setting it to -1, so you can enter the first loop.
In second loop sc didn't ever scan for input it's only initialized.