├── .gitignore ├── .gitattributes ├── LICENSE ├── truffle-config.js ├── truffle.js ├── contracts ├── Migrations.sol ├── Will.sol └── BillOfSale.sol ├── migrations └── 1_initial_migration.js ├── README.md ├── legal-docs └── bill-of-sale.md └── test ├── probate.js └── bill_of_sale_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michael Rice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2018 Michael Rice 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | module.exports = { 12 | // See 13 | // to customize your Truffle configuration! 14 | }; 15 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2018 Michael Rice 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | module.exports = { 12 | description: "A collection of standard Solidity legal contracts", 13 | authors: [ 14 | "Michael Rice " 15 | ], 16 | networks: { 17 | development: { 18 | host: "127.0.0.1", 19 | port: 8545, 20 | network_id: "*" // Match any network id 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2018 Michael Rice 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | pragma solidity ^0.4.24; 12 | 13 | contract Migrations { 14 | address public owner; 15 | uint public last_completed_migration; 16 | 17 | modifier restricted() { 18 | if (msg.sender == owner) _; 19 | } 20 | 21 | constructor() public { 22 | owner = msg.sender; 23 | } 24 | 25 | function setCompleted(uint completed) public restricted { 26 | last_completed_migration = completed; 27 | } 28 | 29 | function upgrade(address new_address) public restricted { 30 | Migrations upgraded = Migrations(new_address); 31 | upgraded.setCompleted(last_completed_migration); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2018 Michael Rice 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | var Migrations = artifacts.require("./Migrations.sol"); 12 | var BillOfSale = artifacts.require("./BillOfSale.sol"); 13 | var Will = artifacts.require("./Will.sol"); 14 | 15 | var additionalTermsIpfsHash = "QmZfwvbQQJzHScguKPPPNLe2Bff9mnTJAFS7w37CqdqwPN"; 16 | 17 | //TODO figure out how to use this to at least deploy to rinkeby 18 | 19 | module.exports = function(deployer, network, accounts) { 20 | deployer.deploy(Migrations); 21 | // in this case the seller and contract owner are the same 22 | //TODO - declare what these are so it's more readable 23 | 24 | let ownerAccount = accounts[0]; 25 | let sellerAccount = ownerAccount; 26 | let buyerAccount = accounts[1]; 27 | 28 | deployer.deploy(BillOfSale, ownerAccount, sellerAccount, buyerAccount, 29 | additionalTermsIpfsHash); 30 | 31 | deployer.deploy(Will, ownerAccount); 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /contracts/Will.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2018 Michael Rice 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | pragma solidity ^0.4.24; 12 | 13 | contract Will { 14 | address public contractOwner; 15 | address public testator; 16 | address public administrator; 17 | mapping(address => uint) public beneficiaries; //uint = share of 100 (no percentages) 18 | 19 | constructor(address _contractOwner) public { 20 | contractOwner = _contractOwner; 21 | } 22 | 23 | function designateTestator(address _testator) public contractOwnerOnly { 24 | testator = _testator; 25 | } 26 | 27 | function appointAdministrator(address _administrator) public testatorOnly { 28 | administrator = _administrator; 29 | } 30 | 31 | function addBeneficiary(address beneficiary, uint share) public testatorOnly { 32 | beneficiaries[beneficiary] = share; 33 | } 34 | 35 | modifier contractOwnerOnly() { 36 | require(msg.sender == contractOwner, "only contract owner may call this function"); 37 | _; 38 | } 39 | 40 | modifier testatorOnly() { 41 | require(testator != address(0), "testator must be defined first"); 42 | require(msg.sender == testator, "only testator may call this function"); 43 | _; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solidity legal contracts 2 | 3 | ## Open source smart contracts with legal terms that you can take and use 4 | 5 | This project's goal is to make a collection of open source Ethereum-based smart contracts combined with traditional, court-tested terms to fill in the legal gaps left behind by the [Solidity](http://solidity.readthedocs.io) code. Put differently, goal is to create fully **legally binding smart contracts**. 6 | 7 | The first example contract is a bill of sale with additional terms. I hope to add many more in the future. 8 | 9 | ## EXPERIMENTAL 10 | 11 | **Keep in mind** they're just models and ideas right now. Even though I'm a lawyer and I think I'm on the right track, I'm not necessarily convinced of their legal effectiveness and certainly can't possibly know if they're the right tools for you. 12 | 13 | **No warranties!** This is open source software that has not been tested. Use at your own risk. 14 | 15 | ## Contact me with ideas 16 | 17 | Please contact me if you have any questions: [michael@michaelricelaw.com](mailto:michael@michaelricelaw.com) or submit issues here on Github. 18 | 19 | Would love to hear your feedback or learn from your ideas. 20 | 21 | ## Requirements 22 | 23 | The project was set up using the [Truffle Framework](http://truffleframework.com). Make sure you're running truffle **4.1.11**, which uses Solidity version **0.4.24**. 24 | 25 | 26 | Make sure you have it installed locally (also, it requires Node). You'll like it. Truffle is good. 27 | 28 | Also, the test suite uses the newer async/await features of node so you'll need version 9x installed. Check your version first: 29 | 30 | ``` 31 | node --version 32 | ``` 33 | 34 | ## Running Locally 35 | 36 | If you want to play around with the contracts, make sure you run the tests. 37 | 38 | First start up the local test network 39 | 40 | ``` 41 | ganache-cli 42 | ``` 43 | 44 | Then run the tests: 45 | 46 | ``` 47 | truffle test 48 | ``` 49 | 50 | ## Notes 51 | 52 | ### Regarding the property received flag: 53 | 54 | One of the features of the contract as written is that the buyer has to declare when he or she received the property. I think it's necessary to prevent the funds from being withdrawn without the buyer confirming receipt, yet it could create a situation where the buyer never sets the flag and the seller is never able to receive his or her funds. Seems to me this could be solved with an oracle service to, say, a check a shipment confirmation from UPS or Fedex (or whatever our locality uses) or some kind of smart property IoT connection. 55 | 56 | For now, instead of doing all that, I tried to solve it with by adding additional legal terms in the [bill_of_sale.md](../blob/master/legal-docs/bill-of-sale.md) to declare that title did not formally pass until property is received and the the contract could be "unwound" if the buyer doesn't perform by setting the field within a reasonable period of time. As I write this, I'm not sure it's actually legally effective but I think it's the right direction. 57 | 58 | The technical problem I have, as I write this, is that I'm not yet sure how to safely return the funds to the buyer in that instance from the smart contract. Added GitHub issue [no. 3](https://github.com/mrice/solidity-legal-contracts/issues/3) if you wnt to help out!! 59 | -------------------------------------------------------------------------------- /legal-docs/bill-of-sale.md: -------------------------------------------------------------------------------- 1 | 2 | # Bill of Sale 3 | 4 | **NOTICE:** The following instrument is an open source document freely available on the internet. Important notes and conditions on its use are stored at https://github.com/mrice/solidity-legal-contracts. Any users of this document or the Solidity smart contract code agree to those terms of use. 5 | 6 | ## 1. Composition 7 | 8 | This agreement ("**Agreement**") is comprised in part through this document ("**Document**") and in part through a smart contract ("**Smart Contract**") which will be deployed on the Ethereum network ("**Blockchain**") to an address known to the Agreement's parties. Both the Document and the Smart Contract are integral to the Agreement. 9 | 10 | ## 2. Parties 11 | 12 | There are two parties ("**Parties**") to this Agreement, a buyer ("**Buyer**") and a seller ("**Seller**"). The Parties will be identified by their respective public addresses on the Ethereum network. Buyer's address will be stored in the buyer field of the Smart Contract; Seller's will be stored in the seller field. 13 | 14 | ## 3. Agreement 15 | 16 | Seller agrees to sell an item (the "**Item**") to be described in the personalProperty field of the Smart Contract by either Buyer or Seller for sum of Ethereum cryptographic to be defined by the *salePrice* field of the Smart Contact, which will be defined when the Smart Contract is deployed on the Ethereum network and later funded to the Smart Contract by the Buyer as explained in section 5 *infra*. 17 | 18 | The Item will be transferred by a method documented in the Smart Contract in the methodOfDelivery field and title shall pass to Buyer when this Agreement is fully performed. 19 | 20 | If Seller delivers the Item and Buyer fails to set the *propertyDelivered* field of the Smart Contract as explained in section 5(8) *infra* within a reasonable time then the Seller may demand return of the Item. 21 | 22 | ## 4. No Warranty 23 | 24 | BUYER FURTHER AGREES THAT THE ITEM IS SOLD IN AN "AS IS" CONDITION AND THAT SELLER MAKES NO GUARANTEES OR WARRANTIES, EXPRESS OR IMPLIED, FOR THE CONDITION OF THE ITEM OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. 25 | 26 | ## 5. Performance 27 | 28 | The Agreement will be performed when the Item has been transferred and the following functions have been called and/or fields populated by the Parties to the Smart Contract: 29 | 30 | 1. *seller* field is set; 31 | 2. *buyer* field is set; 32 | 3. the *salePrice* field is set; 33 | 4. a copy of this Document has been stored on an immutable file system and its location stored in the *additionalTerms* field; 34 | 5. *personalProperty* and *deliveryMethod* fields are set; 35 | 6. Buyer funds the contract; 36 | 7. Seller transfers the Item physically or by other means to Buyer; 37 | 8. Buyer confirms delivery of the Item by setting the *propertyDelivered* field to true; and 38 | 9. Seller withdraws the funds from the Smart Contract. 39 | 40 | To the extent permitted by local law, the sequence of events of the Smart Contract or physical delivery of the Item is not important to the Agreement. 41 | 42 | ## 6. Additional Terms 43 | 44 | A. In the event there is a conflict between this Document and the Smart Contract, the terms of the Smart Contract will control. 45 | 46 | B. Each of the Parties is responsible for the his or her own costs (e.g., "gas" on the Ethereum network) for executing transactions against the Smart Contract. 47 | 48 | C. This Agreement constitutes the entire agreement between Parties. 49 | 50 | D. If any provision of this Agreement is held to be unenforceable, the Parties wish that the remaining provisions of the Agreement continue to be enforced. 51 | 52 | E. The terms of this Agreement shall be interpreted by the local laws of the Seller's jurisdiction. Any disputes shall be resolved in a tribunal local to the Seller. Venue shall be at a location determined by Seller within the jurisdiction. 53 | -------------------------------------------------------------------------------- /contracts/BillOfSale.sol: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2018 Michael Rice 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | /** 12 | USAGE NOTES: this is not intended to be a stand alone contract. There are additional 13 | terms intended to be stored in an immutable form on IPFS but available for download 14 | here: 15 | https://github.com/mrice/solidity-legal-contracts/blob/master/legal-docs/bill-of-sale.md 16 | **/ 17 | 18 | pragma solidity ^0.4.24; 19 | 20 | contract BillOfSale { 21 | address public contractOwner; 22 | address public seller; 23 | address public buyer; 24 | uint public salePrice; 25 | string public additionalTerms; 26 | string public personalProperty; 27 | string public deliveryMethod; 28 | bool public sellerAssent = false; 29 | bool public buyerAssent = false; 30 | bool public propertyReceived = false; 31 | bool public fullyPerformed = false; 32 | 33 | constructor(address _contractOwner, address _seller, address _buyer, 34 | string _additionalTerms) public { 35 | contractOwner = _contractOwner; 36 | seller = _seller; 37 | buyer = _buyer; 38 | additionalTerms = _additionalTerms; 39 | } 40 | 41 | event TransactionPerformed(); 42 | 43 | function setSalePrice(uint _salePrice) public sellerOnly { 44 | salePrice = _salePrice; 45 | } 46 | 47 | function setPersonalProperty(string _personalProperty) public sellerOnly { 48 | personalProperty = _personalProperty; 49 | } 50 | 51 | function setDeliveryMethod(string _deliveryMethod) public buyerOrSellerOnly { 52 | deliveryMethod = _deliveryMethod; 53 | } 54 | 55 | function recordSellerAssent() public sellerOnly { 56 | sellerAssent = true; 57 | } 58 | 59 | function recordBuyerAssent() public buyerOnly { 60 | buyerAssent = true; 61 | } 62 | 63 | function confirmPropertyReceived() public buyerOnly performanceReviewed preventIncompleteAssent { 64 | propertyReceived = true; 65 | } 66 | 67 | function () public payable performanceReviewed preventIncompleteAssent { 68 | require(msg.value == salePrice); 69 | } 70 | 71 | function sellerWithdraw() public sellerOnly preventIncompleteAssent { 72 | require(fullyPerformed, "contract must be fully performed before seller withdrawal"); 73 | seller.transfer(address(this).balance); 74 | } 75 | 76 | function kill() public { 77 | if (msg.sender == contractOwner) { 78 | selfdestruct(contractOwner); 79 | } 80 | } 81 | 82 | /** 83 | m o d i f i e r s - cross cutting concerns for the bill of sale contract 84 | */ 85 | 86 | modifier sellerOnly() { 87 | require(msg.sender == seller, "only seller can send this message"); 88 | _; 89 | } 90 | 91 | modifier buyerOrSellerOnly() { 92 | require(msg.sender == buyer || msg.sender == seller, "only buyer or seller can send this message"); 93 | _; 94 | } 95 | 96 | modifier buyerOnly() { 97 | require(msg.sender == buyer, "only buyer can send this message"); 98 | _; 99 | } 100 | 101 | modifier preventIncompleteAssent() { 102 | require(sellerAssent == true && buyerAssent == true); 103 | _; 104 | } 105 | 106 | /** 107 | functions with this modifier could change contract state to fully performed 108 | */ 109 | modifier performanceReviewed() { 110 | _; 111 | if (propertyReceived && address(this).balance == salePrice) { 112 | fullyPerformed = true; 113 | emit TransactionPerformed(); 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /test/probate.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2018 Michael Rice 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | var Will = artifacts.require("./Will.sol", 1); 12 | 13 | contract('Probate...', async (accounts) => { 14 | 15 | var will; 16 | var contractOwner = accounts[0]; 17 | var testatorAccount = accounts[1]; 18 | var administratorAccount = accounts[2]; 19 | var daughter1Account = accounts[3]; 20 | var strangerAccount = accounts[9]; 21 | 22 | beforeEach("create a new instance of the will each time", async() => { 23 | will = await Will.new(contractOwner); 24 | }); 25 | 26 | it ("deploys and we can prove truth", async() => { 27 | let will = await Will.deployed(); 28 | assert.isTrue(true); 29 | }); 30 | 31 | it ("should have the contractOwner assigned at deploy time", async() => { 32 | let assignedOwner = await will.contractOwner(); 33 | expect(assignedOwner).to.be.defined; 34 | expect(assignedOwner).to.be.a.string; 35 | expect(assignedOwner).to.equal(contractOwner); 36 | }); 37 | 38 | it ("allows contractOwner to define testator", async() => { 39 | await will.designateTestator(testatorAccount, {from:contractOwner}); 40 | let assignedTestator = await will.testator(); 41 | expect(assignedTestator).to.be.defined; 42 | expect(assignedTestator).to.be.a.string; 43 | expect(assignedTestator).to.equal(testatorAccount); 44 | }); 45 | 46 | it ("prevents anyone but contractOwner from defining testator", function() { 47 | return Will.new(contractOwner).then(function(will) { 48 | return will.designateTestator.call(testatorAccount, {from: strangerAccount}); 49 | }).then(function (noErrorThrown) { 50 | assert.isTrue(false, "should have failed"); 51 | }, function (errorThrown) { 52 | assert.isTrue(true, "failure caught"); 53 | }); 54 | }); 55 | 56 | it ("allows the testator to appoint an administrator", async() => { 57 | await will.designateTestator(testatorAccount, {from:contractOwner}); 58 | await will.appointAdministrator(administratorAccount, {from:testatorAccount}); 59 | let assignedAdministrator = await will.administrator(); 60 | expect(assignedAdministrator).to.be.defined; 61 | expect(assignedAdministrator).to.be.a.string; 62 | expect(assignedAdministrator).to.equal(administratorAccount); 63 | }); 64 | 65 | it ("prevents anyone but the testator from appointing an administrator", async() => { 66 | return Will.new(contractOwner).then(function(will) { 67 | return will.appointAdministrator(administratorAccount, {from: strangerAccount}); 68 | }).then(function (noErrorThrown) { 69 | assert.isTrue(false, "should have failed"); 70 | }, function (errorThrown) { 71 | assert.isTrue(true, "failure caught"); 72 | }); 73 | }); 74 | 75 | it ("lets the testator add a beneficiary", async() => { 76 | await will.designateTestator(testatorAccount, {from: contractOwner}); 77 | await will.appointAdministrator(administratorAccount, {from: testatorAccount}); 78 | 79 | await will.addBeneficiary(daughter1Account, 50, {from: testatorAccount}); 80 | 81 | let beneficiaries = await will.beneficiaries; 82 | expect(beneficiaries).to.be.defined; 83 | 84 | let assignedShareBigNumber = await will.beneficiaries(daughter1Account); 85 | let derivedShare = assignedShareBigNumber.toNumber(); 86 | expect(derivedShare).to.equal(50); 87 | 88 | }); 89 | 90 | it ("prevents anyone but the testator from adding a beneficiary", async() => { 91 | 92 | let will = await Will.new(contractOwner); 93 | await will.designateTestator(testatorAccount, {from: contractOwner}); 94 | 95 | will.addBeneficiary(daughter1Account, 50, {from: strangerAccount}).then( 96 | function(noErrorThrown) { 97 | assert.isTrue(false, "should have failed"); 98 | }, function(errorThrown) { 99 | assert.isTrue(true, "failure caught"); 100 | }); 101 | }); 102 | 103 | }); 104 | -------------------------------------------------------------------------------- /test/bill_of_sale_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright 2018 Michael Rice 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | */ 10 | 11 | var BillOfSale = artifacts.require("./BillOfSale.sol", 1); 12 | 13 | contract('Bill of Sale...', async (accounts) => { 14 | 15 | var billOfSale; 16 | var contractOwnerAccount = accounts[0]; 17 | var sellerAccount = accounts[0]; 18 | var buyerAccount = accounts[1]; 19 | var strangerAccount = accounts[2]; 20 | var saleAmount = 5 * 1000000000000000000; //5eth, right? 21 | var additionalTermsIpfsHash = "QmZfwvbQQJzHScguKPPPNLe2Bff9mnTJAFS7w37CqdqwPN"; 22 | 23 | //deploys a new contract on each test to try to keep the tests isolated 24 | beforeEach('get reference to bill of sale before each test', async() => { 25 | billOfSale = await BillOfSale.new(contractOwnerAccount, sellerAccount, 26 | buyerAccount, additionalTermsIpfsHash); 27 | }); 28 | 29 | it("deploys and asserts to true", async () => { 30 | let bos = await BillOfSale.deployed(); 31 | assert.isTrue(true); 32 | }); 33 | 34 | it ("has a contractOwner set at deployment", async () => { 35 | let contractOwner = await billOfSale.contractOwner(); 36 | assert.isTrue(contractOwner == sellerAccount, "expected: " + sellerAccount + " got: " + contractOwner); 37 | }); 38 | 39 | it ("has seller set at deployment", async () => { 40 | let seller = await billOfSale.seller(); 41 | assert.isTrue(seller == sellerAccount, "expected: " + sellerAccount + " got: " + seller); 42 | }); 43 | 44 | it ("has buyer set at deployment", async () => { 45 | let buyer = await billOfSale.buyer(); 46 | assert.isTrue(buyer == buyerAccount, "expected: " + buyerAccount + " got: " + buyer); 47 | }); 48 | 49 | it ("has additionalTerms set at deployment", async () => { 50 | let additionalTerms = await billOfSale.additionalTerms.call(); 51 | assert.isOk(additionalTerms, "expected ok; got: " + additionalTerms); 52 | }); 53 | 54 | it ("does not let the someone other than seller define salePrice", function() { 55 | return BillOfSale.deployed().then(function(bos) { 56 | return bos.setSalePrice.call(saleAmount, {from: buyerAccount}) 57 | }).then(function (noErrorThrown) { 58 | assert.isTrue(false, "should have failed"); 59 | }, function (errorThrown) { 60 | assert.isTrue(true, "failure caught"); 61 | }); 62 | }); 63 | 64 | it ("allows the seller to define the salePrice", async() => { 65 | await billOfSale.setSalePrice(saleAmount, {from: sellerAccount}); 66 | let assignedSalePrice = await billOfSale.salePrice.call().valueOf(); 67 | 68 | assert.isTrue(assignedSalePrice == saleAmount, "sale price should have been set"); 69 | }); 70 | 71 | it ("allows the seller to define the chattel", async() => { 72 | await billOfSale.setPersonalProperty("solidity legal forms", {from: sellerAccount}); 73 | let assignedPersonalProperty = await billOfSale.personalProperty.call().valueOf(); 74 | 75 | assert.isTrue(assignedPersonalProperty == "solidity legal forms", "personalProperty not set correctly (got: " + assignedPersonalProperty + ")") 76 | }); 77 | 78 | //TODO - couldn't figure out how to make assert.throws() work here; went with this 79 | it ("does not let the buyer define the chattel", function() { 80 | return BillOfSale.deployed().then(function(bos) { 81 | return bos.setPersonalProperty.call("chattel", {from: buyerAccount}) 82 | }).then(function (noErrorThrown) { 83 | assert.isTrue(false, "should have failed"); 84 | }, function (errorThrown) { 85 | assert.isTrue(true, "failure caught"); 86 | }); 87 | }); 88 | 89 | it ("allows seller to define the delivery method", async() => { 90 | await billOfSale.setDeliveryMethod("fedex overnight", {from: sellerAccount}); 91 | let assignedDM = await billOfSale.deliveryMethod.call().valueOf(); 92 | 93 | assert.isTrue(assignedDM == "fedex overnight", "deliveryMethod not set correctly (got: " + assignedDM + ")") 94 | }); 95 | 96 | it ("allows buyer to define the method of delivery", async() => { 97 | await billOfSale.setDeliveryMethod("fedex overnight", {from: buyerAccount}); 98 | let assignedDM = await billOfSale.deliveryMethod.call().valueOf(); 99 | 100 | assert.isTrue(assignedDM == "fedex overnight", "deliveryMethod not set correctly (got: " + assignedDM + ")") 101 | }); 102 | 103 | //TODO - couldn't figure out how to make assert.throws() work here; went with this 104 | it ("fails if a stranger to the contract tries define delivery method", function() { 105 | return BillOfSale.deployed().then(function(bos) { 106 | return bos.setDeliveryMethod.call("fedex", {from: strangerAccount}) 107 | }).then(function (noErrorThrown) { 108 | assert.fail("should have failed"); 109 | }, function (errorThrown) { 110 | assert.isTrue(true, "failure caught"); 111 | }); 112 | }); 113 | 114 | it ("allows the seller to manifest asset", async() => { 115 | await billOfSale.recordSellerAssent({from: sellerAccount}); 116 | let sellerAssent = await billOfSale.sellerAssent.call().valueOf(); 117 | assert.isOk(sellerAssent, "seller assent should be recorded"); 118 | }); 119 | 120 | it ("throws error if someone other than seller tries to assent for seller", function() { 121 | return BillOfSale.deployed().then(function(bos) { 122 | return bos.recordSellerAssent.call({from: strangerAccount}); 123 | }).then(function(noErrorThrown) { 124 | assert.fail("should have failed"); 125 | }, function (errorThrown) { 126 | assert.isTrue(true, "failure caught"); 127 | }); 128 | }); 129 | 130 | it ("allows the buyer to manifest asset", async() => { 131 | await billOfSale.recordBuyerAssent({from: buyerAccount}); 132 | let buyerAssent = await billOfSale.buyerAssent.call().valueOf(); 133 | assert.isOk(buyerAssent, "buyer assent should be recorded"); 134 | }); 135 | 136 | it ("throws error if someone other than buyer tries to assent for buyer", function() { 137 | return BillOfSale.deployed().then(function(bos) { 138 | return bos.recordBuyerAssent.call({from: strangerAccount}); 139 | }).then(function(noErrorThrown) { 140 | assert.fail("should have failed"); 141 | }, function (errorThrown) { 142 | assert.isTrue(true, "failure caught"); 143 | }); 144 | }); 145 | 146 | it ("fails if someone tries to declare the property was received without full assent", function() { 147 | return BillOfSale.deployed().then(function(bos) { 148 | return bos.confirmPropertyReceived.call({from:buyerAccount}); 149 | }).then(function (noErrorThrown) { 150 | assert.fail("should have failed"); 151 | }, function (errorThrown) { 152 | assert.isTrue(true, "failure caught"); 153 | }); 154 | }); 155 | 156 | it ("throws error if seller tries to withdraw before buyer confirms receipt", function() { 157 | return BillOfSale.deployed().then(function(bos) { 158 | return bos.sellerWithdraw.call({from: sellerAccount}) 159 | }).then(function(noErrorThrown) { 160 | assert.fail("should have failed"); 161 | }, function (errorThrown) { 162 | assert.isTrue(true, "failure caught"); 163 | }); 164 | }); 165 | 166 | it ("allows the buyer to declare that the property was received", async() => { 167 | await billOfSale.setSalePrice(saleAmount, {from: sellerAccount}); 168 | await billOfSale.recordSellerAssent({from: sellerAccount}); 169 | await billOfSale.recordBuyerAssent({from: buyerAccount}); 170 | await billOfSale.confirmPropertyReceived({from: buyerAccount}); 171 | let propertyReceived = await billOfSale.propertyReceived.call().valueOf(); 172 | 173 | assert.isTrue(propertyReceived , "the propertyReceived flag was not set even though it should have been") 174 | }); 175 | 176 | it ("fails if anyone other than buyer account tries to set the property received flag", function() { 177 | return BillOfSale.deployed().then(function(bos) { 178 | return bos.confirmPropertyReceived.call({from:strangerAccount}) 179 | }).then(function (noErrorThrown) { 180 | assert.isTrue(false, "should have failed"); 181 | }, function (errorThrown) { 182 | assert.isTrue(true, "failure caught"); 183 | }); 184 | }); 185 | 186 | it ("fails if the wrong amount gets paid to the contract", function() { 187 | return BillOfSale.deployed().then(function(bos) { 188 | var wrongAmount = saleAmount + 2; //TODO - someday figure out why adding only one doesn't work. mind blown 189 | return bos.sendTransaction({from: buyerAccount, value: wrongAmount}); 190 | }).then(function(noErrorThrown) { 191 | assert.fail("should have failed"); 192 | }, function (errorThrown) { 193 | assert.isTrue(true, "failure caught"); 194 | }); 195 | }); 196 | 197 | it ("allows anyone to fund the contract", async() => { 198 | await billOfSale.setSalePrice(saleAmount, {from: sellerAccount}); 199 | await billOfSale.recordSellerAssent({from: sellerAccount}); 200 | await billOfSale.recordBuyerAssent({from: buyerAccount}); 201 | 202 | //first the contract has to be in a state where the buyer received the property 203 | await billOfSale.confirmPropertyReceived({from: buyerAccount}); 204 | 205 | await billOfSale.sendTransaction({from: buyerAccount, value: saleAmount}); 206 | let bosAddress = await billOfSale.address 207 | assert.isTrue(web3.eth.getBalance(bosAddress).toNumber() == saleAmount); 208 | }); 209 | 210 | //TODO - research whether test cases always perform in order 211 | it ("indicates whether the parties performed", async() => { 212 | await billOfSale.setSalePrice(saleAmount, {from: sellerAccount}); 213 | await billOfSale.recordSellerAssent({from: sellerAccount}); 214 | await billOfSale.recordBuyerAssent({from: buyerAccount}); 215 | 216 | // first condition is confirmation of property received 217 | await billOfSale.confirmPropertyReceived({from: buyerAccount}); 218 | 219 | // second is payment of ether 220 | await billOfSale.sendTransaction({from: buyerAccount, value: saleAmount}); 221 | 222 | let fullyPerformed = await billOfSale.fullyPerformed.call(); 223 | assert.isTrue(fullyPerformed, "contract should be fully performed at this point"); 224 | }); 225 | 226 | it ("throws an error if someone other than seller tries to withdraw", function() { 227 | return BillOfSale.deployed().then(function(bos) { 228 | return bos.sellerWithdraw.call({from: strangerAccount}) 229 | }).then(function(noErrorThrown) { 230 | assert.fail("should have failed"); 231 | }, function (errorThrown) { 232 | assert.isTrue(true, "failure caught"); 233 | }); 234 | }); 235 | 236 | it ("lets the seller withdraw the funds if the contract is fully performed", async() => { 237 | await billOfSale.setSalePrice(saleAmount, {from: sellerAccount}); 238 | await billOfSale.recordSellerAssent({from: sellerAccount}); 239 | await billOfSale.recordBuyerAssent({from: buyerAccount}); 240 | 241 | // first condition is confirmation of property received 242 | await billOfSale.confirmPropertyReceived({from: buyerAccount}); 243 | 244 | // second is payment of ether 245 | await billOfSale.sendTransaction({from: buyerAccount, value: saleAmount}); 246 | 247 | let sellerOldAccountBalance = web3.eth.getBalance(sellerAccount).toNumber(); 248 | 249 | await billOfSale.sellerWithdraw({from: sellerAccount}); 250 | let sellerNewAccountBalance = web3.eth.getBalance(sellerAccount).toNumber(); 251 | 252 | assert.isTrue((sellerNewAccountBalance > sellerOldAccountBalance), "seller's account should increase"); 253 | //TODO - to do this right we'd be accurately predicting and testing the new balance after gas cost 254 | 255 | let bosAddress = await billOfSale.address 256 | let bosBalance = web3.eth.getBalance(bosAddress).toNumber(); 257 | assert.isTrue(bosBalance == 0, "smart contract balance should be zero"); 258 | 259 | }); 260 | 261 | //TODO - add the case where someone other than the seller tries to withdraw 262 | //TODO - add case whether seller tries to withdraw w/o fullyPerformed flag set (higher up) 263 | 264 | }); 265 | 266 | //TODO (backlog) do not let the delivery method change once defined 267 | 268 | //TODO - missing a test case where description, price, and delivery method need to be defined before assent!! 269 | --------------------------------------------------------------------------------