$ mkdir fundraiser
$ cd fundraiser
$ truffle unbox react
$ tree
$ rm contracts/SimpleStorage.sol \
migrations/2_deploy_contracts.js \
test/*
$ touch contracts/Fundraiser.sol test/fundraiser_test.js
受取人の名前、Webサイト、受取人のアドレス、トップページのカードに使う画像URL、受取人に関する説明、管理人またはownerのアドレス
fundraiser_test.js
const FundraiserContract = artifacts.require("Fundraiser");
contract("Fundraiser", accounts => {
let fundraiser;
const name = "Beneficiary Name";
const url = "beneficiaryname.org";
const imageURL = "https://placekitten.com/600/350";
const description = "Beneficiary description";
const beneficiary = accounts[1];
const owner = accounts[0];
beforeEach(async() => {
fundraiser = await FundraiserContract.new(
name,
url,
imageURL,
description,
beneficiary,
owner
);
});
describe("initialization", () => {
it("gets the beneficiary name", async() => {
const actual = await fundraiser.name();
assert.equal(actual, name, "names should match");
});
it("gets the beneficiary url", async() => {
const actual = await fundraiser.url();
assert.equal(actual, url, "url should match");
});
it("gets the beneficiary image url", async() => {
const actual = await fundraiser.imageURL();
assert.equal(actual, imageURL, "imageURL should match");
});
it("gets the beneficiary description", async() => {
const actual = await fundraiser.description();
assert.equal(actual, description, "description should match");
});
it("gets the beneficiary", async() => {
const actual = await fundraiser.beneficiary();
assert.equal(actual, beneficiary, "beneficiary addresses should match");
});
it("gets the owner", async() => {
const actual = await fundraiser.owner();
assert.equal(actual, owner, "bios should match");
})
});
describe("setBeneficiary", () => {
const newBeneficiary = accounts[2];
it("updated beneficiary when called by owner account", async() => {
await fundraiser.setBeneficiary(newBeneficiary, {from: owner});
const actualBeneficiary = await fundraiser.beneficiary();
assert.equal(actualBeneficiary, newBeneficiary,
"beneficiaries should match");
})
it("throws an error when called from a non-owner account", async() => {
try {
await fundraiser.setBeneficiary(newBeneficiary, {from: accounts[3]});
assert.fail("withdraw was not restricted to owners")
} catch(err) {
const expectedError = "Ownable: caller is not the owner"
const actualError = err.reason;
assert.equal(actualError, expectedError, "should not be permitted")
}
})
});
describe("making donations", () => {
const value = web3.utils.toWei('0.0289');
const donor = accounts[2];
it("increases myDonationsCount", async() => {
const currentDonationsCount = await fundraiser.myDonationsCount(
{from: donor}
);
await fundraiser.donate({from: donor, value});
const newDonationsCount = await fundraiser.myDonationsCount(
{from: donor}
);
assert.equal(
1,
newDonationsCount - currentDonationsCount,
"myDonationsCount should increment by 1"
);
});
it("includes donation in myDonations", async() => {
await fundraiser.donate({from: donor, value});
const {values, dates} = await fundraiser.myDonations(
{from: donor}
);
assert.equal(
value,
values[0],
"values should match"
);
assert(dates[0], "date should be present");
});
it("increases the totalDonations amaount", async() => {
const currentTotalDonations = await fundraiser.totalDonations();
await fundraiser.donate({from: donor, value});
const newTotalDonations = await fundraiser.totalDonations();
const diff = newTotalDonations - currentTotalDonations;
assert.equal(
diff,
value,
"difference should match the donation value"
);
});
it("increases donationsCount", async() => {
const currentDonationsCount = await fundraiser.donationsCount();
await fundraiser.donate({from: donor, value});
const newDonationsCount = await fundraiser.donationsCount();
assert.equal(
1,
newDonationsCount - currentDonationsCount,
"donationsCount should increment by 1"
)
})
it("emits the DonationReceived event", async() => {
const tx = await fundraiser.donate({from: donor, value});
const expectedEvent = "DonationReceived";
const actualEvent = tx.logs[0].event;
assert.equal(actualEvent, expectedEvent, "events should match");
})
});
describe("withdrawing funds", ()=> {
beforeEach(async() => {
await fundraiser.donate(
{from: accounts[2], value: web3.utils.toWei('0.1')}
);
});
describe("access controls", () => {
it("throws an error when called from a non-owner account", async() => {
try {
await fundraiser.withdraw({from: accounts[3]});
assert.fail("withdraw was not restricted to owners");
} catch(err) {
const expectedError = "Ownable: caller is not the owner";
const actualError = err.reason;
assert.equal(actualError, expectedError, "should not be permitted");
}
});
it("permits the owner to call the function", async() => {
try {
await fundraiser.withdraw({from: owner});
assert(true, "no errors were thrown");
} catch(err) {
assert.fail("should not have thrown an error");
}
})
});
it("transfers balance to beneficiary", async() => {
const currentContractBalance = await web3.eth.getBalance(fundraiser.address);
const currentBeneficiaryBalance = await web3.eth.getBalance(beneficiary);
await fundraiser.withdraw({from: owner});
const newContractBalance = await web3.eth.getBalance(fundraiser.address);
const newBeneficiaryBalance = await web3.eth.getBalance(beneficiary);
const beneficiaryDifference = newBeneficiaryBalance - currentBeneficiaryBalance;
assert.equal(
newContractBalance,
0,
"contract should have a 0 balance"
);
assert.equal(
beneficiaryDifference,
currentContractBalance,
"beneficiary should receive all the funds"
);
});
it("emit Withdraw event", async()=>{
const tx = await fundraiser.withdraw({from: owner});
const expectedEvent = "Withdraw";
const actualEvent = tx.logs[0].event;
assert.equal(
actualEvent,
expectedEvent,
"events should match"
);
});
});
describe("fallback function", () => {
const value = web3.utils.toWei('0.0289');
it("increases the totalDonation amount", async() => {
const currentTotalDonations = await fundraiser.totalDonations();
await web3.eth.sendTransaction(
{to: fundraiser.address, from: accounts[9], value}
);
const newTotalDonations = await fundraiser.totalDonations();
const diff = newTotalDonations - currentTotalDonations;
assert.equal(
diff,
value,
"difference should match the donation value"
);
});
it("increase donationsCount", async() => {
const currentDonationsCount = await fundraiser.donationsCount();
await web3.eth.sendTransaction(
{to: fundraiser.address, from: accounts[9], value}
);
const newDonationsCount = await fundraiser.donationsCount();
assert.equal(
1,
newDonationsCount - currentDonationsCount,
"donationsCount should increment by 1"
);
});
});
});
Fundraiser.sol
pragma solidity >0.4.23 <=0.8.0;
import "openzeppelin-solidity/contracts/access/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/math/SafeMath.sol";
contract Fundraiser is Ownable{
using SafeMath for uint256;
uint256 public totalDonations;
uint256 public donationsCount;
event DonationReceived(address indexed donor, uint256 value);
event Withdraw(uint256 amount);
struct Donation {
uint256 value;
uint256 date;
}
mapping(address => Donation[]) private _donations;
string public name;
string public url;
string public imageURL;
string public description;
address payable public beneficiary;
constructor(
string memory _name,
string memory _url,
string memory _imageURL,
string memory _description,
address payable _beneficiary,
address _custodian
)
public {
name = _name;
url = _url;
imageURL = _imageURL;
description = _description;
beneficiary = _beneficiary;
transferOwnership(_custodian);
}
function setBeneficiary(address payable _beneficiary) public onlyOwner {
beneficiary = _beneficiary;
}
function myDonationsCount() public view returns(uint256){
return _donations[msg.sender].length;
}
function donate() public payable {
Donation memory donation = Donation({
value: msg.value,
date: block.timestamp
});
_donations[msg.sender].push(donation);
totalDonations = totalDonations.add(msg.value);
donationsCount++;
emit DonationReceived(msg.sender, msg.value);
}
function myDonations() public view returns(
uint256[] memory values,
uint256[] memory dates
){
uint256 count = myDonationsCount();
values = new uint256[](count);
dates = new uint256[](count);
for (uint256 i = 0; i < count; i++){
Donation storage donation = _donations[msg.sender][i];
values[i] = donation.value;
dates[i] = donation.date;
}
return (values, dates);
}
function withdraw() public onlyOwner {
uint256 balance = address(this).balance;
beneficiary.transfer(balance);
emit Withdraw(balance);
}
fallback () external payable {
totalDonations = totalDonations.add(msg.value);
donationsCount++;
}
}
address型とaddress payable型の二つのアドレスがある
少しずつ理解してきました