Solidity based "BIKE RENTAL SHOP" on Ethereum network.
Bike Rental Shop

About
This repository contains the source code and dependencies required to deploy a Solidity based "BIKE RENTAL SHOP" on Ethereum network.
Overview
The following diagram shows a high level overview of the interaction between the main components and actors in the bike rental service:

Main features
This is a summary with the main features implemented in the BikeRental contract:
- Billing: the rental fee is calculated as a function of time (i.e. duration of rental in seconds)
- Payment medium: customers can choose to pay with Ether or Tokens
- Payment priority: BikeRental contract will try to fullfil pending payment with Token funds from customer's TokenAccount as a first priority. If payment is not cancelled 100%, contract will try to fulfill pending payment with Ether funds from customer's EtherAccount balance as a second priority.
- Collateral rate: a premium rate (i.e. 20% cheaper than standard rate) is applicable to those customers that choose to fund their tokenAccount or etherAccount above a predefined collateral level (i.e.
etherCollateralThresholdortokenCollateralThreshold). - Un-spent funds: Token or Ether amount that is not spent when rental finishes, is automatically returned/refunded to customers.
- Customer debt: if customer rental fee is greater than available funds, a debt is generated and customer is banned from renting again until debt is cancelled.
- Contract upgradeability: additional functions are implemented to enable the update of main business parameters (i.e. minimum balance required, rate, collateral threshold), that only BikeRental owner is allowed to execute:
function setRate(uint _rate) external onlyOwner
function setCollateralThreshold(uint _threshold) external onlyOwner
function setEtherMinimumBalance(uint _etherMin) external onlyOwner
function setTokenConversionRate(uint8 _conversion) external onlyOwner
function setCollateralPremium(uint8 _premium) external onlyOwner
Rental service start / stop
- The rental service starts when customer calls function
startRental() - The rental service is finished when customer calls function
stopRental()
Smart contracts
The following two smart contracts are required to implement the Bike Rental Shop service:
1. BikeRental.sol
Is the main contract, that implements the business logic for the BikeRental Shop.
- Main state variables:
Token token; //Reference to deployed ERC20 Token contract
address payable wallet; //Address of the owner of Bike Rental Shop
uint8 collateralPremium=20; // discount % to be applied to standard rate for PREMIUM "collateralized customers"
uint8 tokenConversionRate = 2; //conversion rate between Ether and Token, i.e. 1 Ether = 2 Token
uint rate=10000; //amount of wei to be charged per second (i.e. "standard rate")
uint etherMinBalance=60000; //minimum amount of ETH required to start bike rental
uint tokenMinBalance= 120000; //minimum amount of Tokens required to start bike rental
uint etherCollateralThreshold=500000; //Threshold amount of ETH required to get PREMIUM "collateralized" rate
uint tokenCollateralThreshold=1000000; //Threshold amount of Tokens required to get PREMIUM "collateralized" rate
mapping (address => Customer) customers ; // Record with customers data (i.e., balance, startTie, debt, rate, etc)
mapping (uint8 => Bike) bikes ; // Available stock of bikes
- Main external functions:
//@dev: buyTokens used by customer to purchase rental Tokens in exchange of Ether
function buyTokens() payable public
//@dev: startRental function is called by customers to start the bike rental service //@params: bikeId is id of bike to rent function startRental (uint8 _bikeId) payable public{
//@dev: stopRental function is called by customers to stop the bike rental service function stopRental () external {
2. Token.sol
- ERC20 token compliance smart contract
- Addon functions:
function burn(address to, uint256 amount) onlyOwner external
function mint(address to, uint256 amount) onlyOwner external
Payment model
All customers have 2 accounts at BikeRentalShop: etherAccount and tokenAccount.
And they can choose to pay the rental service with ether, tokens or both.
Below is included a description of each payment model:
Paying with ETHER
- If the customer chooses to pay bike rental with Ether, he/she calls
startRental()(payable) and sends Ether to BikeRental.sol contract in order to provide funds to the customerEtherAccount.
- When the customer returns the bike, he/she calls
stopRental(). - At this point the BikeRental.sol contract will debit the total fee from the customerEtherAccount, and credit it to the rental shop owner's account.
- The remaining (un-spent) ether funds are transferred back to the customer.
Paying with TOKENS
- If the customer wishes to pay bike rental with Tokens, first he/she should purchase Tokens with Ether by calling
buyTokens()(payable). The BikeRental.sol contract sends the received Ether to the owner of the bike rental shop. - Then customer should
approve()the BikeRental.sol contract to transfer the desired amount of tokens into the customerTokenAccount. - When customer wants to start the rental, he/she calls
startRental()and the BikeRental.sol contract will execute the transfer of approved tokens to the customerTokenAccount.
- When the customer returns the bike, he/she calls
stopRental(). - At this point the BikeRental.sol contract will debit the total fee from the customerTokenAccount, and transfer the amount of tokens back to the BikeRental pool of tokens.
- The remaining (un-spent) Token funds are transferred back to the customer.
Gas optimization
The following is a short list of some considerations taken into account in order to minimize cost of gas
- Enforcement of debt cancelling before rental to avoid revert after updating balances
uint8type storage allocation where possiblebytetype instead ofstringtype- use of function
externalinstead ofpublic - use events to keep track of data off-chain (i.e. record of bike details)
Further improvements
- Implement a minimum rental fee (applicable when rental elapsed time is below i.e. 15 minutes)
- Deploy an off-chain Oracle that updates the rental rate according to real time traffic statistics
- Set rate according to time of day (i.e., low, medium, high)
- Develop additional use case tests
- Usage of SafeMath library
Use case tested
The following are the use cases currently implemented in the truffle test suite
- Use Case1: customer transfer Ether (above collateral threshold) and starts/stops bike rent (
BikeRental.test1.js) - Use Case2: customer buys tokens, approves transfer of tokens, and starts/stops bike rent (
BikeRental.test2.js) - Use Case3: customer buys Tokens, approves transfer of tokens, starts/stops bike rent, debt is created, and customer transfer additional Ether to cancel debt (
BikeRental.test3.js) - Use Case4: customer buys Tokens, approves transfer of tokens, transfer Ether, and starts/stops bike rent (
BikeRental.test4.js)
Usage
Dependencies
- Truffle
- Node.js
- npm
- Ganache Cli
Installation
git clone https://github.com/alejoacosta74/BikeRentalShop bikeRentalShop
cd bikeRentalShop
npm install
truffle init
Compile
truffle compile
On a separate terminal start Ganache-CLI:
ganache-cli -m "<seed phrase>" -h 0.0.0.0
Migrate and deploy to Ganache
truffle migrate --network development
Run tests
truffle test --network development //run test suite against Ganache
Expected test outcome
Contract: BikeRentalShop contract -> UseCase1: customer transfer Ether (above collateral threshold) and starts/stops bike rent
At contract deployment
โ Owner should transfer all tokens to BikeRental (107ms)
UseCase1: #1. customer transfer Ether (above collateral threshold) and starts bike rent
โ should update customer rental account by amount transferred (81ms)
โ should update rental ether balance by amount transfered
โ should emit event RentalStart
โ should emit event BalanceUpdated
โ customer should get premium "collateralized" rate (89ms)
UseCase1: #2. customer finishes bike rental
Rental elapsed time: 13
โ Un-spent ether should be returned to customer
โ Customer rental account balance should be zero (131ms)
โ owner balance of ether should be incremented by ether worth equal to debited amount
โ should emit event RentalStop
โ should emit event DebtUpdated
โ should emit event FundsReturned
Contract: BikeRentalShop contract -> UseCase2: customer buys tokens, approves transfer of tokens, and starts/stops bike rent At contract deployment โ Owner should transfer all tokens to BikeRental (139ms) UseCase2: #1. customer buys tokens โ should increase owner Ether balance with same amount of Ether that customer sent to purchase tokens โ should decrease customer Ether balance with same amount of Ether that customer sent to purchase tokens UseCase2: #2. customer transfer Tokens (above collateral threshold) and starts bike rent โ should update customer rental account of tokens by amount transferred (129ms) โ should update rental balance of tokens by amount transferred (138ms) โ should emit event RentalStart โ should emit event BalanceUpdated โ customer should get premium "collateralized" rate (131ms) UseCase2: #3. customer stops bike rent Rental elapsed time: 13 โ Un-spent tokens should be returned to customer (112ms) โ Token balance of bikeRental should be incremented by amount of debited tokens (94ms) โ Customer rental account balance should be set to zero (111ms) โ should emit event RentalStop โ should emit event DebtUpdated โ should emit event FundsReturned
Contract: BikeRentalShop contract -> UseCase3: customer buys Tokens, approves transfer of tokens, starts/stops bike rent, debt is created, and customer transfer additional Ether to cancel debt At contract deployment โ Owner should transfer all tokens to BikeRental (106ms) UseCase3: #1. customer buys tokens โ should increase owner Ether balance with same amount of Ether that customer sent to purchase tokens โ should decrease customer Ether balance with same amount of Ether that customer sent to purchase tokens UseCase3: #2. customer starts bike rent โ should update customer rental account of tokens by amount transferred (143ms) โ should update rental balance of tokens by amount transferred (120ms) โ should emit event RentalStart โ should emit event BalanceUpdated โ customer should get standard rate (115ms) UseCase3: #3. customer stops bike rent and debt is generated Rental elapsed time: 17 โ Standing debt should be equal to rental fee minus tokens transferred โ Debited token amount from customer should be equal to tokens transferred amount (109ms) โ Customer rental account balance should be set zero (77ms) โ should emit event RentalStop โ should emit event DebtUpdated โ should NOT emit event FundsReturned UseCase3: #4. Customer tries to rent again while debt is pending โ startRent should be reverted if debt is pending (741ms) UseCase3: #5. customer transfer Ether and debt is cancelled โ owner balance of ether should be incremented by ether worth equal to debited amount โ customer standing debt should be 0 (zero) (108ms) โ customer ether account balance should be 0 (zero) (83ms) โ should emit event FundsReceived โ should emit event BalanceUpdated โ should emit event DebtUpdated UseCase3: #6. customer transfer extra Ether โ owner balance of ether should NOT be incremented if debt is zero โ customer standing debt should be 0 (zero) (76ms) โ customer ether account balance should equal to amount of Ether transferred (76ms) โ should emit event FundsReceived โ should emit event BalanceUpdated โ should NOT emit event DebtUpdated
Contract: BikeRentalShop contract -> UseCase4: customer buys Tokens, approves transfer of tokens, transfer Ether, and starts/stops bike rent At contract deployment โ Owner should transfer all tokens to BikeRental (81ms) UseCase4: #1. customer buys tokens โ should increase owner Ether balance with same amount of Ether that customer sent to purchase tokens โ should decrease customer Ether balance with same amount of Ether that customer sent to purchase tokens UseCase4: #2. customer sends 40.000 wei and starts bike rent โ should update customer rental account of tokens by amount transferred (78ms) โ should update rental balance of tokens by amount transferred (96ms) โ should emit event RentalStart โ should emit event BalanceUpdated โ customer should get standard rate (83ms) UseCase4: #3. customer stops bike rent, price is deducted from Tokens and Ether and remaining funds are returned Rental elapsed time: 15 โ Debited token amount from customer should be equal to tokens transferred amount โ Debited ether amount from customer should be equal to total fee minus tokens debited from customer โ Customer rental Token account balance should be set zero (115ms) โ should emit event RentalStop โ should emit event DebtUpdated โ should emit event FundsReturned
67 passing (47s)
BTC address
bc1q89aalael3kr5vhjn6ss4g4fscxnesc9gw8sfyryhzq7nhdphlm9semr93f
@alejoacosta74