Hey @mervxxgotti
Have you tried to debug by console.log()
the result?
Give it a try and let us know the results.
Cheers,
Dani
Hey @mervxxgotti
Have you tried to debug by console.log()
the result?
Give it a try and let us know the results.
Cheers,
Dani
Iâm sorry Iâm quite confused.
I think I am confused with how Solidity interacts with JS. I am more familiar with Solidity where it is similar to C++ and the variables are typed. But Iâve never used JS and am confused on how variables are stored or assumed if no type is explicitly assigned.
When I am in truffle console, when I enter:
truffle(ganache)> let instance = await People.deployed()
instance is now a People contract, correct? And then when I enter:
truffle(ganache)> instance.createPerson(âBobâ, 65, 90, {value: web3.utils.toWei(â1â, âetherâ)})
Now instance stores this struct of type Person into the mapping pointed to by the address of the instance contract, yes?
Then,
truffle(ganache)> let result = await instance.getPerson()
And now result is a struct of type Person with all the attributes of the Person struct pointed to by instance address?
Now how do I use console.log() in this setting? When I console.log(result), are age and height given in hex? Is this why we need to use toNumber() and toString()? Is there something wrong with these functions?
Here is my output for:
truffle(ganache)> let instance = await People.deployed()
truffle(ganache)> await instance.createPerson(âBobâ, 65, 90, {value: web3.utils.toWei(â1â, âetherâ)})
truffle(ganache)> let result = await instance.getPerson()
truffle(ganache)> console.log(result)
and i recieve:
Result { â0â: âBobâ, â1â: <BN: 41>, â2â: <BN: 5a>, â3â: true }
but when I use console.log(result.name) or console.log(result.age), it only returns undefined. When I add toString() or toNumber() at the end of the arguments, it reports those properties as undefined.
and when I add console.log(result) to the test .js file and run the test, it shows the senior being true so I think something is wrong with the assert statements as well as the toString?
const People = artifacts.require("People");
const truffleAssert = require("truffle-assertions");
contract("People", async function(){
//test and assert contract shouldn't make person with age over 150
it("shouldn't make person with age over 150", async function() {
let instance = await People.deployed();
//use assert to test failure, asserting that createPerson fails if age 200 since it's >150
await truffleAssert.fails(
instance.createPerson("Bob", 200, 300, {value: web3.utils.toWei("1", "ether")}),
truffleAssert.ErrorType.REVERT //test passes upon REVERT, we WANT to assert contract REVERT if age >150
);
})
//test and assert contract shouldn't create person without valid payment amount
it("shouldn't create a person without payment", async function(){
let instance = await People.deployed();
await truffleAssert.fails(
instance.createPerson("Bob", 50, 300, {value: 1000}),
truffleAssert.ErrorType.REVERT //test passes upon REVERT, we WANT to assert contract REVERT if age >150
);
})
//test and assert contract should set senior flag correctly
it("should set senior status correctly", async function() {
//create person with age >65
let instance = await People.deployed();
await instance.createPerson("Bob", 65, 300, {value: web3.utils.toWei("1", "ether")});
let result = await instance.getPerson();
console.log(result);
assert(result.senior === true, "should be senior!");
})
it("should set age correctly", async function() {
let instance = await People.deployed();
let result = await instance.getPerson();
console.log(result);
assert(result.age.toNumber() === 65, "Age not set correctly!");
})
});
Hey @mervxxgotti
I am going to divide your question above and answer one by one to your questions.
truffle(ganache)> let instance = await People.deployed()
instance is now a People contract, correct?
With that declaration let instance = await People.deployed()
you have basically created an attraction of your People contract:
instance
now contains the same functions that exist within our contract and does also contain the address that points to your People contract deployed on the blockchain.
Basically, you will use instance
to invoke any functions in your contract, and you will also know what is the contract address that holds these functions.
truffle(ganache)> instance.createPerson(âBobâ, 65, 90, {value: web3.utils.toWei(â1â, âetherâ)})
Now instance stores this struct of type Person into the mapping pointed to by the address of the instance contract, yes?
Now consider what I wrote above and read this call instance.createPerson(âBobâ, 65, 90, {value: web3.utils.toWei(â1â, âetherâ)})
.
You are basically creating a new transaction in the blockchain, this transaction contains the call to the function createPerson
.
The data that you are sending are store in the blockchain. Instance is just a pointer to your contract, but the data are saved in the contract itself.
Then,
truffle(ganache)> let result = await instance.getPerson()
Here you are doing the same as above, but now you are getting info from your contract and you are saving these info in a variable called result
.
When you console.log(result)
you are reading the data that the blockchain sent you.
Now a practical example, consider this test contract:
pragma solidity 0.5.12;
contract Testing {
struct TestIsFun {
uint testNumber1;
uint testNumber2;
}
mapping (uint => TestIsFun) public testStruct;
function setStruct () public {
testStruct[0].testNumber1 = 50;
testStruct[0].testNumber2 = 100;
}
function getStruct () public view returns (uint, uint) {
return (testStruct[0].testNumber1, testStruct[0].testNumber2);
}
}
In the truffle console:
declare an instance of my contract:
let i = await Testing.deployed()
Call the function setStruct:
await i.setStruct()
Call the function getStruct
and save the result in r
:
let r = await i.getStruct()
Console.logÂŽ
console.log( r ) Result { '0': <BN: 32>, '1': <BN: 64> }
Notice that the result above is an object, this object at 0 and 1 as properties.
struct TestIsFun {
uint testNumber1;
uint testNumber2;
}
0 is testNumber1
1 is testNumber2
In order to read only the 1st value:
console.log(r[0])
result is <BN: 32>
Now letâs convert the big number to a number (should be 50)
Here it is
Now try to fix your tests and keep me posted!
Happy coding,
Dani
Thank you for being so thorough!
I understand now up until console.log()
How come I also get that result listing the struct when I simply type in >result into the terminal without console.log()?
When I use console.log(result), it gives me that result as well, along with all this other stuff:
So it shows me there that the senior boolean is indeed true, but how come when I use console.log(parseInt[1]) to find the age, it returns NaN? :
Thanks!
Hey @mervxxgotti
console.log is not an async function, you should not use âawaitâ.
Also the javascript console prints data even without console.log(), you see the same behaviour if you open it in your browser
Itâs really strange that you get not a number when you parse the bn 41, can you post your code here so that I can copy paste/ deploy and check?
cheers,
Dani
pragma solidity 0.5.12;
//pragma experimental ABIEncoderV2;
import "./Ownable.sol";
contract People is Ownable {
struct Person {
string name;
uint age;
uint height;
bool senior;
}
event personCreated(string name, uint age, uint height, bool senior);
event personDeleted(string name, bool senior, address deletedBy);
uint public balance;
modifier costs(uint cost) {
require(msg.value >= cost);
_;
}
mapping (address => Person) private people;
address[] private creators;
//costs 1 ether to run
function createPerson(string memory _name, uint _age, uint _height) public payable costs(1 ether) {
require(_age < 150, "TOO OLD!");
//require(msg.value >= 1 ether); //requires 1 wei to execute
//balance += msg.value; //add to balance value that was sent to this function/contract
Person memory newPerson;
newPerson.name = _name;
newPerson.age = _age;
newPerson.height = _height;
if(_age >= 65) {
newPerson.senior = true;
} else {
newPerson.senior = false;
}
insertPerson(newPerson);
creators.push(msg.sender);
//assert using to == compare hash of both using keccak256 hash function
//essentially checking if people[msg.sender] == newPerson
assert(
keccak256(
abi.encodePacked(
people[msg.sender].name,
people[msg.sender].age,
people[msg.sender].height,
people[msg.sender].senior
)
)
==
keccak256(
abi.encodePacked(
newPerson.name,
newPerson.age,
newPerson.height,
newPerson.senior
)
)
);
emit personCreated(newPerson.name, newPerson.age, newPerson.height, newPerson.senior);
}
function insertPerson(Person memory _person) private {
address creator = msg.sender;
people[creator] = _person;
}
/*for use with ABIEncoderV2
function getPerson() public view returns (Person memory) {
return people[msg.sender];
}*/
function getPerson() public view returns (string memory, uint, uint, bool) {
address creator = msg.sender;
return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior);
}
function deletePerson(address creator) public onlyOwner {
string memory name = people[creator].name;
bool senior = people[creator].senior;
delete people[creator];
assert(people[creator].age == 0);
emit personDeleted(name, senior, msg.sender);
}
function getCreator(uint index) public view onlyOwner returns (address) {
return creators[index];
}
function withdrawAll() public onlyOwner returns (uint) {
uint toTransfer = balance; //make temp var to send, reset balance to 0, done for security reasons
balance = 0;
msg.sender.transfer(toTransfer); //transfer can only be used by an address, transfers to address, which in this case
//is the owner, or msg.sender if already here. Reverts if transfer fails
/*
msg.sender.send(toTransfer); //this is the same as transfer but returns false if fails instead of reverts.
//hence, this requires manual reverting or else funds are lost.
if(msg.sender.send(toTransfer)) {
return toTransfer;
} else {
balance = toTransfer;
return 0;
}
*/
return toTransfer;
}
}
Dani! I think I got it!
I took a look at Filipâs uploaded code and this was the difference I spotted but am still confused by:
function getPerson() public view returns(string memory name, uint age, uint height, bool senior){
address creator = msg.sender;
return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior);
}
compared to mine:
function getPerson() public view returns(string memory, uint, uint, bool){
address creator = msg.sender;
return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior);
}
I added names to the returns arguments and it returned correctly now but how come in other instances when using returns() only the types are defined without names? For example in:
function getCreator(uint index) public view onlyOwner returns (address) {
return creators[index];
}
function withdrawAll() public onlyOwner returns (uint) {
uint toTransfer = balance; //make temp var to send, reset balance to 0, done for security reasons
balance = 0;
msg.sender.transfer(toTransfer); //transfer can only be used by an address, transfers to address, which in this case
//is the owner, or msg.sender if already here. Reverts if transfer fails
return toTransfer;
}
Why is this and when are we required to add names as well as types in the returns() part of the function header? Do we do this when returning multiple variables? But I remember before we had something like returns (uint, uint) in other header functions.
Do we have to define return variable names when returning multiple struct members specifically?
it("should not allow delete person unless owner", async function() {
let instance = await People.deployed();
//create person from account 1 since account 0 is contract owner defined in deployment
await instance.createPerson("Bob", 65, 90, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
//test failure, account 1 cannot delete even if it made person, only owner (address 0) can delete
await truffleAssert.fails(
instance.deletePerson(accounts[1], {from: accounts[1]}),
truffleAssert.ErrorType.REVERT
);
})
it("should allow owner to delete people", async function(){
let instance = await People.deployed();
await truffleAssert.passes(
instance.deletePerson(accounts[1], {from: accounts[0]})
);
})
Hey @mervxxgotti
Do we have to define return variable names when returning multiple struct members specifically?
It is not mandatory to define names of your returned values.
Only types are mandatory.
Cheers,
Dani
Hey, I havenât gotten the test comparing balances before and after withdrawal working yet, but I have some questions about âawaitâ as I think it has something to do with that.
So when I use vs not use âawaitâ,
let a = web3.eth.getBalance(accounts[0]) vs let b = await web3.eth.getBalance(accounts[0])
both return the account balance in wei when I check the values of a and b, but then they behave very differently.
How come when when I parse a (without await) it doesnât read a as a string, but then it does with b (with await?
If this is the case, how come when I use:
let newPerson = instance.getPerson()
it only actually assigns the values to newPerson if I defined the return names in the getPerson() header? When I didnât have the names and only had the types in the returns() part, newPerson.age and newPerson.height were returning as NaN?
Itâs working now but I just am curious and want to know for my own knowledge.
I created a test and wanted to assert that every wei is transferred to the address it should be.
However, it seems I am calculating something wrong here:
const People = artifacts.require("People");
const truffleAssert = require("truffle-assertions");
contract("People", async (accounts) => {
let instance;
let oneEther = web3.utils.toWei("1", "ether");
beforeEach(async () => {
instance = await People.new();
});
// ... several other tests...
it("preventy anyone but owner to withdraw balance", async () => {
let twoEther = oneEther * 2;
let owner = accounts[0];
let alex = accounts[1];
let mike = accounts[2];
let initialOwnerBalance = parseFloat(await web3.eth.getBalance(owner));
await instance.createPerson("Alex", 36, 180, {value: twoEther, from:alex});
await instance.createPerson("Mike", 32, 185, {value: oneEther, from:mike});
// Act
await truffleAssert.fails(instance.withdrawAll({from:alex}));
await truffleAssert.fails(instance.withdrawAll({from:mike}));
let withdrawn = await instance.withdrawAll.call(({from:owner}));
let txn = await instance.withdrawAll({from:owner});
// Assert
// let gasUsed = parseFloat(txn.receipt.gasUsed);
let threeEther = oneEther*3;
let ownerBalance = parseFloat(await web3.eth.getBalance(owner));
let contractBalance = parseFloat(await web3.eth.getBalance(instance.address));
assert(withdrawn == threeEther, "should withdraw 3 eth but was "+withdrawn);
// why the hell is there a difference of 0,00041?? GAS?
assert(ownerBalance == (initialOwnerBalance+withdrawn),
"initial: "+web3.utils.fromWei(""+initialOwnerBalance)+" + withdrawn: "+web3.utils.fromWei(""+withdrawn)+" = after: "+web3.utils.fromWei(""+ownerBalance));
assert(ownerBalance > initialOwnerBalance, "owner should have more eth now...");
assert(contractBalance == 0, "contract balance should be drained but was " + contractBalance);
})
});
So apparently, when looking at the owners balance, there is some wei missing. Can anybody explain why?
If ETH may be worth 20K some day, I would rather know that my contract sends just the right amount of wei obviously
anytime i want to test my contract it keep failing
const People = artifacts.require(âPeopleâ);
const truffleAssert = require(âtruffle-assertionsâ);
contract(âPeopleâ, async function(){
it(âshould process smoothlyâ , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson(âgabbyâ ,70 , 160 , {value: web3.utils.toWei(â1â, âetherâ)}),truffleAssert.ErrorType.REVERT);
})
it(âshould undergo correctlyâ , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson(âlisaâ , 68 ,200 , {value: web3.utils.toWei(â1â, âetherâ)}),truffleAssert.ErrorType.REVERT);
})
it(âshould undergo correctlyâ , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson(âlisaâ , 68 ,200 , {value: 1000000000000000000 , from: account[0]}),truffleAssert.ErrorType.REVERT);
})
it("should not be allowed to delete person " , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson(âandrewâ , 98 ,200 , { from: account[2], value: web3.utils.toWei(â1â, âetherâ)}),truffleAssert.ErrorType.REVERT);
await truffleAssert.fails(instance.deletePerson(accounts[2], {from: account[2]}),truffleAssert.ErrorType.REVERT);
})
it(âonlyowner should be allowed to delete personâ , async function(){
let instance = await People.deployed();
await truffleAssert.passes(instance.deletePerson(account[2], { from: account[0]}));
})
});
type or paste code here
Hi @beloved
Please follow this faq to post readable code: FAQ - How to post code in the forum
Can you tell us which test is failing and whatâs the error message? Itâs also helpful post your smart contract.
Regards,
Dani
Here is the contract coding u asked for
pragma solidity 0.5.12;
contract Ownable{
address public owner;
modifier onlyOwner(){
require(msg.sender == owner);
_; //continue execution
}
constructor() public {
owner = msg.sender;
}
}
contract People is Ownable{
struct Person {
uint id;
string name;
uint age;
uint height;
bool senior;
}
event personCreated(string name, bool senior);
event personDeleted(string name, bool senior, address deletedBy);
uint public balance;
modifier costs(uint cost){
require(msg.value >= cost);
_;
}
mapping (address => Person) private people;
address[] private creators;
function createPerson(string memory name, uint age, uint height) public payable costs(1 ether){
require(age < 150, âAge needs to be below 150â);
require(msg.value >= 1 ether);
balance += msg.value;
//This creates a person
Person memory newPerson;
newPerson.name = name;
newPerson.age = age;
newPerson.height = height;
if(age >= 65){
newPerson.senior = true;
}
else{
newPerson.senior = false;
}
insertPerson(newPerson);
creators.push(msg.sender);
assert(
keccak256(
abi.encodePacked(
people[msg.sender].name,
people[msg.sender].age,
people[msg.sender].height,
people[msg.sender].senior
)
)
==
keccak256(
abi.encodePacked(
newPerson.name,
newPerson.age,
newPerson.height,
newPerson.senior
)
)
);
emit personCreated(newPerson.name, newPerson.senior);
}
function insertPerson(Person memory newPerson) private {
address creator = msg.sender;
people[creator] = newPerson;
}
function getPerson() public view returns(string memory name, uint age, uint height, bool senior){
address creator = msg.sender;
return (people[creator].name, people[creator].age, people[creator].height, people[creator].senior);
}
function deletePerson(address creator) public onlyOwner {
string memory name = people[creator].name;
bool senior = people[creator].senior;
delete people[creator];
assert(people[creator].age == 0);
emit personDeleted(name, senior, owner);
}
function getCreator(uint index) public view onlyOwner returns(address){
return creators[index];
}
function withdrawAll() public onlyOwner returns(uint) {
uint toTransfer = balance;
balance = 0;
msg.sender.transfer(toTransfer);
return toTransfer;
}
}
when ever i test it it doesn't pass
here is the test coding
const People = artifacts.require("People");
const truffleAssert = require("truffle-assertions");
contract("People", async function(){
it("should process smoothly" , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson("gabby" ,70 , 160 , {value: web3.utils.toWei("1", "ether")}),truffleAssert.ErrorType.REVERT);
})
it("should undergo correctly" , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson("lisa" , 68 ,200 , {value: web3.utils.toWei("1", "ether")}),truffleAssert.ErrorType.REVERT);
})
it("should undergo correctly" , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson("lisa" , 68 ,200 , {value: 1000000000000000000 , from: account[0]}),truffleAssert.ErrorType.REVERT);
})
it("should not be allowed to delete person " , async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson("andrew" , 98 ,200 , { from: account[2], value: web3.utils.toWei("1", "ether")}),truffleAssert.ErrorType.REVERT);
await truffleAssert.fails(instance.deletePerson(accounts[2], {from: account[2]}),truffleAssert.ErrorType.REVERT);
})
it("onlyowner should be allowed to delete person" , async function(){
let instance = await People.deployed();
await truffleAssert.passes(instance.deletePerson(account[2], { from: account[0]}));
})
});
type or paste code here
type or paste code here
const People = artifacts.require("People");
const truffleAssert = require("truffle-assertions");
contract("People", async function(accounts) {
it("Only the owner should be able to delete a person.", async function(){
let instance = await People.deployed();
await instance.createPerson("Bob", 65, 170, {from: accounts[1], value: web3.utils.toWei("1", "ether")});
await truffleAssert.fails(instance.deletePerson(accounts[1], {from: accounts[1]}), truffleAssert.ErrorType.REVERT);
})
it("Only the owner should be able to delete person.", async function(){
let instance = await People.deployed();
await truffleAssert.passes(instance.deletePerson(accounts[2], {from: accounts[0]}));
})
Here is my value assignment. The address of the contract is retrieved using the instance.address and parseInt is used to convert the value to an integer to perform the logic required to check the balances.
const People = artifacts.require("People");
const truffleAssert = require("truffle-assertions");
contract("People", async function(accounts) {
it("Balance is correctly updated when new person is created.", async function(){
let previous_balance = await web3.eth.getBalance(instance.address);
await instance.createPerson("Bob", 65, 170, {value: web3.utils.toWei("1", "ether")});
let current_balance = await web3.eth.getBalance(instance.address);
assert(parseInt(current_balance) === parseInt(previous_balance) + 1000000000000000000);
});
it("Balance should be zero after owner performs withdrawAll", async function(){
let previous_balance = await web3.eth.getBalance(instance.address);
await instance.withdrawAll({from: accounts[0]});
let current_balance = await web3.eth.getBalance(instance.address);
assert(parseInt(current_balance) === 0);
})
it("Accounts other than the owner should not be able to withdrawAll", async function(){
let previous_balance = await web3.eth.getBalance(instance.address);
await truffleAssert.fails(instance.withdrawAll({from: accounts[1]}));
})
})