$ 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型の二つのアドレスがある
少しずつ理解してきました