Last active
January 15, 2020 02:38
-
-
Save alexytiger/bad68050575261077931942cf3026602 to your computer and use it in GitHub Desktop.
e-book
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var chai = require('chai'); | |
| chai.use(require('chai-as-promised')).should(); | |
| const BN = web3.utils.BN; | |
| // Enable and inject BN dependency | |
| chai.use(require('chai-bn')(BN)); | |
| var expect = chai.expect; | |
| var assert = chai.assert; | |
| var should = chai.should(); | |
| const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers'); | |
| const { getCurrentTime } = require('./helpers/time'); | |
| const FleaMarketFactory = artifacts.require("../contracts/FleaMarketFactory.sol"); | |
| const SafeRemotePurchase = artifacts.require("../contracts/SafeRemotePurchase.sol"); | |
| // custom function to calculate amount of ether spent on the transaction | |
| // here txInfo is the transaction results | |
| async function getGasCoast(txInfo) { | |
| const tx = await web3.eth.getTransaction(txInfo.tx); | |
| const gasCost = (new BN(tx.gasPrice)).mul(new BN(txInfo.receipt.gasUsed)); | |
| return gasCost; | |
| } | |
| contract("FleaMarketFactory", accounts => { | |
| const [deployer, seller, buyer, buddy] = accounts; | |
| const IPFS_HASH = "QmdXUfpqeGQyvJ6xVouPLR65XtNp63TUHM937zPvg9dFrT"; | |
| // display three test accounts | |
| console.log(`deployer account: ${deployer}`); | |
| console.log(`seller account: ${seller}`); | |
| console.log(`buyer account: ${buyer}`); | |
| describe('deployment of FleaMarketFactory contract', async () => { | |
| let factory; | |
| before(async () => { | |
| factory = await FleaMarketFactory.new(); | |
| }); | |
| beforeEach(async () => { | |
| const time = await getCurrentTime(); | |
| console.log(`current time: ${time}`); | |
| }); | |
| it("deployed successfully", async () => { | |
| const address = await factory.address; | |
| console.log(`contract address: ${address}`); | |
| //make sure the address is real | |
| assert.notEqual(address, 0x0); | |
| assert.notEqual(address, ""); | |
| assert.notEqual(address, null); | |
| assert.notEqual(address, undefined); | |
| }); | |
| it('it has the owner who is the deployer', async () => { | |
| const owner = await factory.owner() | |
| assert.equal(owner, deployer) | |
| }) | |
| }) | |
| describe('creating a new instance of SafeRemotePurchase contract', async () => { | |
| let factory; | |
| beforeEach(async () => { | |
| const time = await getCurrentTime(); | |
| console.log(`current time: ${time}`); | |
| factory = await FleaMarketFactory.new(); | |
| }); | |
| it('should create a valid product', async () => { | |
| const bytes32Key = web3.utils.utf8ToHex('teslaCybertruck-X01'); | |
| const wei = web3.utils.toWei('1.4', 'Ether'); | |
| const commission = new BN(350); | |
| const receipt = await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck', IPFS_HASH, commission, { | |
| from: seller, | |
| value: wei | |
| }); | |
| expect(await factory.getContractCount()).to.be.a.bignumber.that.equal(new BN(1)); | |
| const address = await factory.getContractByKey(bytes32Key); | |
| expectEvent(receipt, 'LogCreatePurchaseContract', { | |
| sender: seller, | |
| contractAddress: address | |
| // key: bytes32Key | |
| }); | |
| const logData = receipt.logs[2]; | |
| const eventData = logData.args; | |
| assert.equal(web3.utils.hexToUtf8(eventData.key), 'teslaCybertruck-X01', "LogCreatePurchaseContract event logged did not have expected product key"); | |
| }) | |
| it('should not create a product for empty key', async () => { | |
| const bytes32Key = web3.utils.utf8ToHex(''); | |
| const wei = web3.utils.toWei('1.4', 'Ether'); | |
| const commission = web3.utils.toBN(350); | |
| await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck', IPFS_HASH, commission, { | |
| from: seller, | |
| value: wei | |
| }).should.be.rejected; | |
| }) | |
| it('should not create a product with the same key', async () => { | |
| const bytes32Key = web3.utils.utf8ToHex('teslaCybertruck-X01'); | |
| const wei = web3.utils.toWei('1.4', 'Ether'); | |
| const commission = web3.utils.toBN(350); | |
| await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck', IPFS_HASH, commission, { | |
| from: seller, | |
| value: wei | |
| }).should.be.fulfilled; | |
| await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck II', IPFS_HASH, commission, { | |
| from: seller, | |
| value: wei | |
| }).should.be.rejected; | |
| }) | |
| it('should not create product with zero commission', async () => { | |
| const bytes32Key = web3.utils.utf8ToHex('teslaCybertruck-X01'); | |
| const wei = web3.utils.toWei('1.4', 'Ether'); | |
| const commission = new BN(0); | |
| await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck', IPFS_HASH, commission, { | |
| from: seller, | |
| value: wei | |
| }).should.be.rejected; | |
| }) | |
| it('should not able to create a product with zero purchase price', async () => { | |
| const bytes32Key = web3.utils.utf8ToHex('teslaCybertruck-X01'); | |
| const commission = new BN(350); | |
| await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck', IPFS_HASH, commission, { | |
| from: seller, | |
| value: 0 | |
| }).should.be.rejected; | |
| }) | |
| it('should not able to create a product having not even price', async () => { | |
| const bytes32Key = web3.utils.utf8ToHex('teslaCybertruck-X01'); | |
| const commission = new BN(350); | |
| await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck', IPFS_HASH, commission, { | |
| from: seller, | |
| value: 31313131317 | |
| }).should.be.rejected; | |
| }) | |
| }) | |
| describe('business logic for purchase and delivery the product', async () => { | |
| let product; | |
| const bytes32Key = web3.utils.utf8ToHex('teslaCybertruck-X01'); | |
| const wei = web3.utils.toWei('1.4', 'Ether'); | |
| const commission = web3.utils.toBN(350); | |
| beforeEach(async () => { | |
| const time = await getCurrentTime(); | |
| console.log(`current time: ${time}`); | |
| const factory = await FleaMarketFactory.new(); | |
| await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck', IPFS_HASH, commission, { | |
| from: seller, | |
| value: wei | |
| }).should.be.fulfilled; | |
| const address = await factory.getContractByKey(bytes32Key); | |
| //assert.notEqual(address, 0x0); | |
| address.should.not.equal(0x0); | |
| // get instance of the SafeRemotePurchase contract by address | |
| product = await SafeRemotePurchase.at(address); | |
| }); | |
| it('is a valid product', async () => { | |
| // validate key | |
| const key = await product.key(); | |
| const keyAscii = web3.utils.hexToUtf8(key); | |
| keyAscii.should.equal('teslaCybertruck-X01'); | |
| // validate seller | |
| expect(await product.seller()).to.equal(seller); | |
| // validate owner | |
| expect(await product.owner()).to.equal(deployer); | |
| // validate price | |
| const price = (new BN(wei)).div(new BN(2)); | |
| expect(await product.price()).to.be.a.bignumber.that.equal(price); | |
| // validate ballance - balance has to be 2x price | |
| expect(await product.balanceOf()).to.be.a.bignumber.that.equal(price.mul(new BN(2))); | |
| // validate state - should be Created | |
| //note that Solidity enum are converted explicitly to uint ==> will be | |
| //retrieved from web3 as BN. the enum values start from 0. | |
| expect(await product.state()).to.be.a.bignumber.that.equal(new BN(0)); | |
| // check for commission value | |
| expect(await product.commissionRate({ | |
| from: deployer | |
| })).to.be.a.bignumber.that.equal(commission); | |
| }) | |
| it('the buyer should be able to purchase and confirm delivery of product', async () => { | |
| // Buyer makes purchase (put 2x of price) | |
| let txInfo = await product.buyerPurchase({ | |
| from: buyer, | |
| value: wei | |
| }).should.be.fulfilled; | |
| expectEvent(txInfo, 'LogPurchaseConfirmed', { | |
| sender: buyer, | |
| amount: wei //could be an instance of BN or string | |
| }); | |
| // validate buyer | |
| expect(await product.buyer()).to.equal(buyer); | |
| // validate state - should be Locked | |
| expect(await product.state()).to.be.a.bignumber.that.equal(new BN(1)); | |
| // validate smart contract ballance | |
| // balance has to be 4x price = (2x from the seller and 2x from the buyer) | |
| expect(await product.balanceOf()).to.be.a.bignumber.that.equal((new BN(wei)).mul(new BN(2))); | |
| // Buyer confirm delivery | |
| const buyerBalanceBefore = new BN( | |
| await web3.eth.getBalance(buyer) | |
| ); | |
| const price = (new BN(wei)).div(new BN(2)); | |
| txInfo = await product.buyerConfirmReceived({ | |
| from: buyer | |
| }).should.be.fulfilled; | |
| expectEvent(txInfo, 'LogReceivedByBuyer', { | |
| sender: buyer, | |
| amount: price // could be an instance of BN or string | |
| }); | |
| const buyerBalanceAfter = new BN( | |
| await web3.eth.getBalance(buyer) | |
| ); | |
| // calculate amount money the buyer spend on the transaction | |
| const gasCost = await getGasCoast(txInfo); | |
| // validate that the buyer gets his escrow money back | |
| expect(buyerBalanceAfter).to.be.a.bignumber.that.equal(buyerBalanceBefore.add(price).sub(gasCost)); | |
| // validate state - should be ItemReceived | |
| expect(await product.state()).to.be.a.bignumber.that.equal(new BN(3)); | |
| // validate smart contract ballance | |
| // balance has to be 3x price = (1x went back to the buyer) | |
| expect(await product.balanceOf()).to.be.a.bignumber.that.equal(price.mul(new BN(3))); | |
| }) | |
| it('the seller and then the deployer should be able to withdraw their money', async () => { | |
| // Buyer makes purchase (put 2x of price) | |
| await product.buyerPurchase({ | |
| from: buyer, | |
| value: wei | |
| }) | |
| // buyer confirm delivery | |
| await product.buyerConfirmReceived({ | |
| from: buyer | |
| }); | |
| const sellerBalanceBefore = new BN( | |
| await web3.eth.getBalance(seller) | |
| ); | |
| // seller withdraw his escrow money | |
| let txInfo = await product.withdrawBySeller({ | |
| from: seller | |
| }).should.be.fulfilled; | |
| expectEvent(txInfo, 'LogWithdrawBySeller', { | |
| sender: seller | |
| }); | |
| const sellerBalanceAfter = new BN( | |
| await web3.eth.getBalance(seller) | |
| ); | |
| // calculate amount money the seller spent on the transaction | |
| let gasCost = await getGasCoast(txInfo); | |
| const price = (new BN(wei)).div(new BN(2)); | |
| // calculate amount money the seller spent on the commission | |
| const commissionCost = (price.mul(commission)).div(new BN(10000)); | |
| // validate the seller ballance after he withdraw escrow and purchased product money from the contract | |
| expect(sellerBalanceAfter).to.be.a.bignumber.that.equal(sellerBalanceBefore.add(price.mul(new BN(3))).sub(gasCost).sub(commissionCost)); | |
| // validate state - should be SellerPaid | |
| expect(await product.state()).to.be.a.bignumber.that.equal(new BN(4)); | |
| // validate smart contract ballance | |
| // balance has to have only the commission to be paid to the deployer | |
| expect(await product.balanceOf()).to.be.a.bignumber.that.equal(commissionCost); | |
| const deployerBalanceBefore = new BN( | |
| await web3.eth.getBalance(deployer) | |
| ); | |
| // deployer withdraw his commission | |
| txInfo = await product.withdrawByOwner({ | |
| from: deployer | |
| }).should.be.fulfilled; | |
| expectEvent(txInfo, 'LogWithdrawByOwner', { | |
| sender: deployer, | |
| amount: commissionCost | |
| }); | |
| const deployerBalanceAfter = new BN( | |
| await web3.eth.getBalance(deployer) | |
| ); | |
| gasCost = await getGasCoast(txInfo); | |
| // validate the deployer ballance | |
| expect(deployerBalanceAfter).to.be.a.bignumber.that.equal(deployerBalanceBefore.add(commissionCost).sub(gasCost)); | |
| // validate state - should be Completed | |
| expect(await product.state()).to.be.a.bignumber.that.equal(new BN(6)); | |
| // validate smart contract ballance => has to be zero | |
| expect(await product.balanceOf()).to.be.a.bignumber.that.equal(new BN(0)); | |
| }) | |
| it('the deployer and then the seller should be able to withdraw their money', async () => { | |
| // Buyer makes purchase (put 2x of price) | |
| await product.buyerPurchase({ | |
| from: buyer, | |
| value: wei | |
| }) | |
| // buyer confirm delivery | |
| await product.buyerConfirmReceived({ | |
| from: buyer | |
| }); | |
| const deployerBalanceBefore = new BN( | |
| await web3.eth.getBalance(deployer) | |
| ); | |
| // deployer withdraw his commission | |
| let txInfo = await product.withdrawByOwner({ | |
| from: deployer | |
| }).should.be.fulfilled; | |
| // calculate the commission | |
| const price = (new BN(wei)).div(new BN(2)); | |
| const commissionCost = (price.mul(commission)).div(new BN(10000)); | |
| expectEvent(txInfo, 'LogWithdrawByOwner', { | |
| sender: deployer, | |
| amount: commissionCost | |
| }); | |
| const deployerBalanceAfter = new BN( | |
| await web3.eth.getBalance(deployer) | |
| ); | |
| let gasCost = await getGasCoast(txInfo); | |
| // validate the deployer ballance | |
| expect(deployerBalanceAfter).to.be.a.bignumber.that.equal(deployerBalanceBefore.add(commissionCost).sub(gasCost)); | |
| // validate state - should be OwnerPaid | |
| expect(await product.state()).to.be.a.bignumber.that.equal(new BN(5)); | |
| // validate smart contract ballance => should be 3x price minus commission paid to the deployer | |
| expect(await product.balanceOf()).to.be.a.bignumber.that.equal(price.mul(new BN(3)).sub(commissionCost)); | |
| const sellerBalanceBefore = new BN( | |
| await web3.eth.getBalance(seller) | |
| ); | |
| // seller withdraw his escrow money | |
| txInfo = await product.withdrawBySeller({ | |
| from: seller | |
| }).should.be.fulfilled; | |
| expectEvent(txInfo, 'LogWithdrawBySeller', { | |
| sender: seller | |
| }); | |
| const sellerBalanceAfter = new BN( | |
| await web3.eth.getBalance(seller) | |
| ); | |
| // calculate amount money the seller spent on the transaction | |
| gasCost = await getGasCoast(txInfo); | |
| // validate the seller ballance after he withdraw escrow and the purchased product money from the contract | |
| expect(sellerBalanceAfter).to.be.a.bignumber.that.equal(sellerBalanceBefore.add(price.mul(new BN(3))).sub(gasCost).sub(commissionCost)); | |
| // validate state - should be SellerPaid | |
| expect(await product.state()).to.be.a.bignumber.that.equal(new BN(6)); | |
| // validate smart contract ballance | |
| // balance has to be zero | |
| expect(await product.balanceOf()).to.be.a.bignumber.that.equal(new BN(0)); | |
| }) | |
| it('the seller should be able to cancel a purchase contract and reclaim the escrow', async () => { | |
| const sellerBalanceBefore = new BN( | |
| await web3.eth.getBalance(seller) | |
| ); | |
| txInfo = await product.abortBySeller({ | |
| from: seller | |
| }).should.be.fulfilled; | |
| expectEvent(txInfo, 'LogCanceledBySeller', { | |
| sender: seller, | |
| amount: wei | |
| }); | |
| const sellerBalanceAfter = new BN( | |
| await web3.eth.getBalance(seller) | |
| ); | |
| // calculate amount money the seller spend on the transaction | |
| const gasCost = await getGasCoast(txInfo); | |
| // validate that the seller gets his escrow money back | |
| expect(sellerBalanceAfter).to.be.a.bignumber.that.equal(sellerBalanceBefore.add(new BN(wei)).sub(gasCost)); | |
| // validate state - should be Canceled | |
| expect(await product.state()).to.be.a.bignumber.that.equal(new BN(2)); | |
| // validate smart contract ballance - has to be 0 | |
| expect(await product.balanceOf()).to.be.a.bignumber.that.equal(new BN(0)); | |
| }) | |
| }) | |
| describe('some failure cases', async () => { | |
| let product; | |
| const bytes32Key = web3.utils.utf8ToHex('teslaCybertruck-X01'); | |
| const wei = web3.utils.toWei('1.4', 'Ether'); | |
| const commission = web3.utils.toBN(350); | |
| beforeEach(async () => { | |
| const time = await getCurrentTime(); | |
| console.log(`current time: ${time}`); | |
| const factory = await FleaMarketFactory.new(); | |
| await factory.createPurchaseContract(bytes32Key, 'Tesla Cybertruck', IPFS_HASH, commission, { | |
| from: seller, | |
| value: wei | |
| }).should.be.fulfilled; | |
| const address = await factory.getContractByKey(bytes32Key); | |
| //assert.notEqual(address, 0x0); | |
| address.should.not.equal(0x0); | |
| // get instance of the SafeRemotePurchase contract by address | |
| product = await SafeRemotePurchase.at(address); | |
| }); | |
| it('the seller should not be able to cancel a purchase contract in the state other then Created', async () => { | |
| // Buyer makes purchase (put 2x of price) | |
| await product.buyerPurchase({ | |
| from: buyer, | |
| value: wei | |
| }) | |
| // the seller is trying to Abort contract - should be rejected | |
| await product.abortBySeller({ | |
| from: seller | |
| }).should.be.rejected; | |
| }) | |
| it('the buyer is trying to purchase a product with not enough ether', async () => { | |
| // Buyer makes purchase (must deposit 2x of price) | |
| await product.buyerPurchase({ | |
| from: buyer, | |
| value: web3.utils.toWei('1', 'Ether') | |
| }).should.be.rejected; | |
| }) | |
| it('someone else is trying to withdraw money after the buyer confirms the delivery', async () => { | |
| // Buyer makes purchase (put 2x of price) | |
| await product.buyerPurchase({ | |
| from: buyer, | |
| value: wei | |
| }) | |
| // buyer confirm delivery | |
| await product.buyerConfirmReceived({ | |
| from: buyer | |
| }); | |
| // request to withdraw came from the wrong account | |
| await product.withdrawBySeller({ | |
| from: buddy | |
| }).should.be.rejected; | |
| }) | |
| it('should reject if someone trying to view commission rate', async () => { | |
| // only deployer and seller allow | |
| await product.commissionRate({ | |
| from: buddy | |
| }).should.be.rejected; | |
| }) | |
| it('should reject if someone send ether to a purchase contract', async () => { | |
| await web3.eth.sendTransaction({ from: buddy, to: product.address, value: web3.utils.toWei('0.005', "ether") }).should.be.rejected; | |
| }) | |
| it('should reject if someone try to withdraw ether from a purchase contract', async () => { | |
| // notice, we can not provide a contract type address in the 'from' parameter | |
| //otherwise someone would able to withdraw money from the contract | |
| //If we do, we get the error => 'Error: Returned error: sender account not recognized' | |
| await web3.eth.sendTransaction({ from: product.address, to: buddy, value: web3.utils.toWei('0.00005', "ether") }).should.be.rejected; | |
| }) | |
| }) | |
| }) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment