For the sake of getting more practice, I changed a few things here and there. First I changed the migrations file and the contract itself in order to be able to add and display account owners with various account addresses. The reason for this change was my observation that in the process of deploying a contract the only address that msg.sender makes available is the address of the deployer (who, by definition, is also the owner). So a natural question to which I do not know the answer yet is how the address value of msg.sender can be varied. (update:: in watching the solution video, I saw that this problem is solved by using the āfromā specification.)
My solution to the owner-only test is to verify that the personDeleted event has occurred and that the deletedBy parameter is equal to the address of the contract owner (which I found to be equal to accounts[0]). The test seems to be working fine and the tx variable seems to provide the correct transaction identifier. I wonder though what would happen if the deletePerson function had an actual return value. In that case I would expect tx to simply be equal to that return value. So how do we produce correct transaction identifiers in general?
When you look at the code in my migrations file, you will notice that there is some code duplication. I tried to eliminate that duplication by means of an asynchronous function, but when I did so, the migration command was no longer working properly. I believe that there was a problem in coordinating async functions, but I havenāt figured out yet how to resolve that problem.
As a note at the side I would like to add that the deletePerson function does not remove a personās address mapping but merely a personās variable values. In a real-world situation we probably would want to remove the address mapping as well. Is that correct?
So here is my modified contract:
import "./Ownable.sol";
pragma solidity 0.5.12;
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;
uint public numberOfPersons = 0;
modifier costs(uint cost){
require(msg.value >= cost);
_;
}
mapping (address => Person) private people;
address[] private creators;
function getOwner() public view returns(address){
return owner;
}
function getCreators(uint n) public view returns(address){
return creators[n];
}
function getNumberOfPersons()public view returns(uint){
return numberOfPersons;
}
function createPerson(string memory name, uint age, uint height, address
personAddress) public payable costs(1 ether){
require(age < 150, "Age needs to be below 150");
require(msg.value >= 1 ether);
balance += msg.value;
numberOfPersons++;
//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, personAddress);
creators.push(personAddress);
assert(
keccak256(
abi.encodePacked(
people[personAddress].name,
people[personAddress].age,
people[personAddress].height,
people[personAddress].senior
)
}
==
keccak256(
abi.encodePacked(
newPerson.name,
newPerson.age,
newPerson.height,
newPerson.senior
)
)
);
emit personCreated(newPerson.name, newPerson.senior);
}
function insertPerson(Person memory newPerson, address newPersonAddress) private {
address creator = newPersonAddress;
people[creator] = newPerson;
}
function getPerson(address personAddress) public view returns(string memory name,
uint age, uint height, bool senior){
//address creator = msg.sender;
return (people[personAddress].name, people[personAddress].age,
people[personAddress].height, people[personAddress].senior);
}
function deletePerson(address creator) public onlyOwner {
string memory name = people[creator].name;
bool senior = people[creator].senior;
numberOfPersons--;
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;
}
}
And here is my modified migrations file:
const People = artifacts.require("People");
module.exports = function(deployer, networks, accounts) {
deployer.deploy(People).then(async function(instance){
for(n=0; n<10; n++){
console.log("Account #" + n + ": " + accounts[n] + "\n");
}
let ownerAddress = await instance.getOwner();
console.log("Owner: " + ownerAddress + "\n");
for(n=0; n<=2; n++){
await instance.createPerson("Babette" + n, 16+n, 160, accounts[n], {value:
web3.utils.toWei("10", "ether")});
}
let loopNumber = await instance.getNumberOfPersons();
for(n=0; n<loopNumber; n++){
let creatorAddress = await instance.getCreators(n);
let person = await instance.getPerson(creatorAddress);
console.log("Person #" + n +": name: " + person.name + "; age: " + person.age + ";
address: " + creatorAddress + "\n");
}
await instance.deletePerson(accounts[loopNumber-1]);
loopNumber = await instance.getNumberOfPersons();
for(n=0; n<loopNumber; n++){
let creatorAddress = await instance.getCreators(n);
let person = await instance.getPerson(creatorAddress);
console.log("Person #" + n +": name: " + person.name + "; age: " + person.age + ";
address: " + creatorAddress + "\n");
}
});
};
And finally, here is my test file:
const People = artifacts.require("People");
const truffleAssert=require("truffle-assertions");
contract("People", async function(accounts){
it("shouldn't create a person older than 150 years", async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson("Bob", 200, 190, accounts[0], {value:
web3.utils.toWei("1", "ether")}),truffleAssert.ErrorType.REVERT);
});
it("should demand proper payment", async function(){
let instance = await People.deployed();
await truffleAssert.fails(instance.createPerson("Bob", 70, 190, accounts[0], {value:
1000}),truffleAssert.ErrorType.REVERT);
});
it("should set senior status correctly", async function(){
let instance = await People.deployed();
await instance.createPerson("Bob", 70, 190, accounts[0], {value: web3.utils.toWei("1",
"ether")});
let result = await instance.getPerson(accounts[0]);
assert(result.senior===true, "senior status not properly set");
});
it("should emit delete-person event with owner address", async function(){
let instance = await People.deployed();
await instance.createPerson("Babette", 16, 160, accounts[5], {value:
web3.utils.toWei("1", "ether")});
let tx = await instance.deletePerson(accounts[5]);
truffleAssert.eventEmitted(tx, 'personDeleted', {deletedBy: accounts[0]});
});
});