You will complete this Truffle project.
- Your code has to compile, test and be served from within the preconfigured Vagrant box we have made available to your cohort's group on Academy Git. You should also have received an email telling you about it. Do not run any version update on this box.
- You need to upload your project to the Academy Git exam repository named after you, to which you should have received an email invitation.
- You can make as many commits and pushes as you see fit.
- To assist you with configuring your computer to access the repository, please follow Gitlab's official documentation.
With regards to Git, what you can do is a variation on the following:
# Clone the shared repo, replace with proper values. We say upstream
# to remind you that you are not able to push to it.
$ git clone git@git.academy.b9lab.com:YOUR-GROUP/this-repo.git -o upstream
# Go into the new folder
$ cd this-repo
# Notice now it is your-repo-code that you need to change
$ git remote add origin git@git.academy.b9lab.com:YOUR-GROUP/your-repo-code.git
# Populate your
$ git push -u origin master
When the upstream repository has some changes that were added after you did the above, and that you want to take in, you should run:
# Get the changes.
$ git fetch upstream
# Make sure you are on your master
$ git checkout master
# Make sure there are no changes
$ git status
# Merge the changes
$ git merge upstream/master
Our project describes a road system that will be represented by 2 overarching smart contracts:
Regulator
TollBoothOperator
These other elements of the system are represented by externally owned accounts:
- owner of
Regulator
- owner of
TollBoothOperator
- individual vehicles
- individual toll booths
The base price of a route will be determined by 3 variables:
- the entry toll booth
- the exit toll booth
- the vehicle type
These variables will be used thus:
- the
TollBoothOperator
defines a base route price from the entry booth to the exit booth. The base route price from the exit booth to the entry booth may be non-existent, equal or different. - the
TollBoothOperator
defines a multiplier for each vehicle type. This is the number by which the base route price is multiplied with to get the route price or deposit applicable to a specific vehicle.
There are a certain number of off-chain actions, such as proof of identity or secret exchange, that we will address only with a wave of the hand in this project. Smart contracts will make these actions possible via public functions, though.
We only care about vehicles, not about drivers. It means it can be driven by the vehicle's rightful owner, another driver, or no human driver at all.
- Only vehicles registered with the regulator and allowed by the operator are allowed to enter the road system.
- Before entering the road system, registered vehicles must make a deposit of at least the amount required by the operator for their vehicle type.
- Upon exit, the exit toll booth will trigger the payment off the deposit and refund the difference to the vehicle.
Additionally,
- This deposit must be accompanied by the address of the toll booth by which they will enter.
- When exiting the road system, the vehicle gives, off-chain, a secret to the exit toll booth.
- The exit toll booth sends this secret to the toll booth operator contract, which is used to unlock the deposit, then pay the toll booth operator the proper fee for the route taken, then refunds the difference to the vehicle.
The off-chain exchange of the secret is handled by a wave of the hand in this project. You are not asked to work on this massive "detail".
- When faced with a vehicle that wants to enter the road system, a toll booth will confirm, off-chain, the address of the incoming vehicle.
- If the vehicle has made the proper deposit for its type and mentioned the booth address, the booth opens the gate. Tests will assume that this "opens the gate" part is always successful as long as a proper contract call is made.
Here too, you need not work on the off-chain exchange of address of the incoming vehicles.
This account will:
- update the vehicle types in the smart contract.
- ask the smart contract to deploy new
TollBoothOperator
s. - unregister
ToolBoothOperator
s when needed.
This account will:
- update the base deposit it sees fit.
- update the base price of routes for all toll booth pairs it sees fit.
- update the multiplier of all vehicle types it sees fit.
- withdraw collected fees.
In particular:
- if a vehicle just completed a route for which there is a base price of
0
, theTollBoothOperator
owner should put a price to the route before anyone can unlock the deposit. - if a vehicle type has a multiplier of
0
, then the road system will not let vehicles of this type enter. It will let them exit, though.
We encourage you to use the SafeMath
library provided.
This contract does 2 main things (studiously avoiding the word functions here):
- keep track of the vehicle type for each vehicle address
- deploy new
TollBoothOperator
This ensures that:
- because the regulator deploys the toll booth operator, road users are confident they are exchanging with a vetted smart contract.
- an address that has no vehicle type is not registered and should not be allowed onto the road system.
Additionally:
- the regulator collects no fees.
- a type of
0
denotes an unregistered vehicle. - for mnemonics only, you can assign type
1
for motorbikes,2
for cars and3
for lorries (a.k.a. trucks). All values of vehicle type are acceptable, there is no constraint other than> 0
, there is no predefined default. - the latest type is the valid type, even if the regulator changed the type after the vehicle entered a road system.
This contract has many functions which have been parcelled out to its inheritance tree, see its interfaces. The things it does:
- can be paused / resumed to pause the vehicle-facing operations.
- keeps track of the regulator.
- keeps track of the base deposit.
- keeps track of the multipliers of vehicle types.
- keeps track of the toll booths under its purview.
- keeps track of the route base prices.
- accepts deposits from vehicles.
- accepts exit calls from toll booths.
- accepts messages to clear the exit backlog.
- lets vehicles withdraw their refunds.
- lets the owner withdraw the collected fees.
For simplicity's sake:
- there is only 1 (or 0) toll booth at a given kilometre on the road. So one booth may have as many gates as there are lanes on the road.
- a base route price of
0
denotes an absence of information. - a multiplier of
0
denotes an unauthorised vehicle type on the road system. - the entry and exit booths of a route cannot be the same address.
- the
TollBoothOperator
has total control over the deposit, the base route prices, and the multipliers. - there is no congestion or pollution charges.
- the latest route base price is the valid route base price, even if the toll booth operator changed the price after the vehicle entered the road system.
- the multiplier at the time of entry of a given vehicle is the multiplier used for the whole of the trip for said vehicle. In other words, if the toll booth operator changed the multiplier after the vehicle entered the road system, it should have no bearing on this vehicle's trip.
For a vehicle to be accepted on the road system and the operator to be paid at the end of the route, a little dance is engineered:
- before entering the system, the vehicle deposits the required amount into the
TollBoothOperator
, and passes along the address of the entry booth and a unique hashed secret of its own choice. The vehicle keeps the secret until the end of the trip. - when faced with the entry toll booth it mentioned when depositing, it proves its identity off-chain (we talk about this identity proof but you need not implement it), after which the booth opens the gate.
- when exiting the road system at a booth, the vehicle gives the exit toll booth its unhashed secret off-chain. Again, we talk about this off-chain exchange but you do not need to implement it.
- the exit toll booth then submits this secret to the
TollBoothOperator
, which unlocks the deposit for payment and refund of the difference, if applicable. - if the fee is equal to or higher than the deposit, then the whole deposit is used and no more is asked of the vehicle, now or before any future trip.
- if the fee is smaller than the deposit, then the difference is returned to the vehicle.
- if the fee is not known at the time of exit, i.e. if the fee is
0
, the pending payment is recorded, and one "base route price required" event is emitted, and listened to by the operator's oracle. - when the oracle receives a new base route price request, it submits the base fee, which also clears one pending payment.
- if there are more than 1 pending payments on a given route, an additional function is there to progressively clear the backlog a set number of pending payments of said route at a time in a FIFO manner. If both vehicleA and vehicleB entered at the same booths and exited at the same booths, then if vehicleA exited before vehicleB, then vehicleA should pop from the FIFO ahead of vehicleB.
- the vehicle type and multiplier to be used are those at the time of entry.
For simplicity's sake, we have not implemented a deadline after which the deposit is returned to the vehicle. The operator collecting fees, and the refunding of the difference to the vehicle should all use PullPayment
's' asyncPayTo
function, do not use .transfer
.
All interfaces are found in the contracts/interfaces/
folder and are suffixed with I
, for interface, a capital i
, or A
, for abstract. Here is the list of them:
OwnedI
, which keeps track of the owner of a contract.PausableI
, which keeps track of the paused status of a contract.RegulatorI
, which describes methods required of the regulator.RegulatedI
, which keeps track of who the regulator is.MultiplierHolderI
, which keeps track of the multipliers to attach to vehicle types.DepositHolderI
, which keeps track of the base deposit required of vehicles.TollBoothHolderI
, which keeps track of addresses that represent toll booths in the system.RoutePriceHolderI
, which keeps track of the base route prices between entry and exit toll booths.PullPaymentA
, which keeps track of how much is owed to whom.TollBoothOperatorI
, which describes methods required of the toll booth operator for interaction with vehicles.
You should not modify these interface .sol
files.
The contracts you need to create in their individual files in the contracts/
folder are as follows. Note that we will use your implementations against our battery of Truffle unit tests. You can see a sample of them in the test folder. So it is important that you stick to the naming and the parameters order. Also, if you choose to create or use additional Solidity files, like libraries, make sure they are also in contracts/
and not in a subfolder.
We expect you to pass all of the sample tests we have already included. They are here to guide you with your implementation and decide "what is right" in case of uncertainty in this README. If an it
test is marked with this.test.b9MustPass = "failsCode";
and your implementation fails to pass it, we will not fail your project silently, but instead we will come back to you for fixing before going forward. If an it
test is marked with this.test.b9Points = 0;
, it just means that it is a duplicate of another it
that is testing the same in a secret file.
Take note of the stress, or scalability, tests, in which we make sure that your functions are all O(1)
or O(k)
where k
is a function parameter. Your functions should not be O(n)
where n
is an unbounded value not in control of said function's parameters.
Also take note of the mocks. In particular contracts/mock/GreedyRecipient.sol
and NoReEntryRecipient.sol
. They are both used to check that you use .call.value
instead of .transfer
. Yes, this is dangerous. We do this to ascertain you did not leave a reentry attack possible. Yes, use .call.value
instead of .transfer
.
When we say that One
inherits from OneI
and Two
inherits from both OneI
and TwoI
, we leave it to your best judgement as to how to use One
when coding Two
. Remember that inheritance is transitive. Unless mentioned otherwise, all the contracts below are not abstract; this means they need to be deployable on their own.
It extends:
OwnedI
and has:
- a modifier named
fromOwner
that rolls back the transaction if the transaction sender is not the owner. - a constructor that takes no parameter.
It extends:
OwnedI
, remember what we said about transitivity of inheritancePausableI
and has:
- a modifier named
whenPaused
that rolls back the transaction if the contract is in thefalse
paused state. - a modifier named
whenNotPaused
that rolls back the transaction if the contract is in thetrue
paused state - a constructor that takes one
bool
parameter, the initial paused state.
It extends:
OwnedI
RegulatorI
and has:
- a constructor that takes no parameter.
It extends:
RegulatedI
and has:
- a constructor that takes one
address
parameter, the initial regulator; it should roll back the transaction if the initial regulator argument is0
.
It extends:
OwnedI
MultiplierHolderI
and has:
- a constructor that takes no parameter.
It extends:
OwnedI
DepositHolderI
and has:
- a constructor that takes one
uint
parameter, the initial deposit wei value; it should roll back the transaction if the initial deposit argument is0
.
It extends:
OwnedI
TollBoothHolderI
and has:
- a constructor that takes no parameter.
It extends:
OwnedI
TollBoothHolderI
RoutePriceHolderI
and has:
- a constructor that takes no parameter.
This contract is abstract because it extends TollBoothHolderI
but does not implement TollBoothHolderI
's functions. On the other hand RoutePriceHolderMock
does and is not abstract.
It extends:
PullPaymentA
and has:
- a constructor that takes no parameter.
It extends:
OwnedI
PausableI
RegulatedI
MultiplierHolderI
DepositHolderI
TollBoothHolderI
RoutePriceHolderI
PullPaymentA
TollBoothOperatorI
and has:
- a constructor that takes in this order:
- one
bool
parameter, the initial paused state. - one
uint
parameter, the initial deposit wei value; it should roll back the transaction if the initial deposit argument is0
. - one
address
parameter, the initial regulator; it should roll back the transaction if the initial regulator argument is0
.
- one
- a fallback function that rejects all incoming calls, whether they come with weis or without.
additionally, it overrides:
RoutePriceHolder.setRoutePrice
according to the specs found inTollBoothOperatorI.sol
.PullPayment.withdrawPayment
according to the specs found inTollBoothOperatorI.sol
.
You need to create one migration script 2_...js
that will:
- deploy a regulator,
- then call
createNewOperator
on it. - then resume the newly created operator, which should be paused before the resume step.
A quick note on wording:
- "a / the deposit" refers to the value that the contract requires from entering vehicles.
- "what is deposited" means what was actually sent by the vehicle upon entering, which can be equal to or larger than the required deposit.
You may create as many test files as you want, they will help you create the contract implementations but will not be part of the grading. However do not modify or rename the existing tests that were in the repository at the beginning; they are here to guide you.
There is an empty test file named test/scenarios.js
. In this test file, please write exactly 6 tests, one for each of the following 6 scenarios. You can have as many before
, beforeEach
, describe
, afterEach
and after
as you want, however you need to have exactly 6 it
in this test/scenarios.js
file.
- Scenario 1:
vehicle1
enters atbooth1
and deposits required amount (say 10).vehicle1
exits atbooth2
, which route price happens to equal the deposit amount (so 10).vehicle1
gets no refund.
- Scenario 2:
vehicle1
enters atbooth1
and deposits required amount (say 10).vehicle1
exits atbooth2
, which route price happens to be more than the deposit amount (say 15).vehicle1
gets no refund.
- Scenario 3:
vehicle1
enters atbooth1
and deposits required amount (say 10).vehicle1
exits atbooth2
, which route price happens to be less than the deposit amount (say 6).vehicle1
gets refunded the difference (so 4).
- Scenario 4:
vehicle1
enters atbooth1
and deposits (say 14) more than the required amount (say 10).vehicle1
exits atbooth2
, which route price happens to equal the deposit amount (so 10).vehicle1
gets refunded the difference (so 4).
- Scenario 5:
vehicle1
enters atbooth1
and deposits (say 14) more than the required amount (say 10).vehicle1
exits atbooth2
, which route price happens to be unknown.- the operator's owner updates the route price, which happens to be less than the deposited amount (say 11).
vehicle1
gets refunded the difference (so 3).
- Scenario 6:
vehicle1
enters atbooth1
and deposits more (say 14) than the required amount (say 10).vehicle1
exits atbooth2
, which route price happens to be unknown.vehicle2
enters atbooth1
and deposits the exact required amount (so 10).vehicle2
exits atbooth2
, which route price happens to be unknown.- the operator's owner updates the route price, which happens to be less than the required deposit (so 6).
vehicle1
gets refunded the difference (so 8).- someone (anyone) calls to clear one pending payment.
vehicle2
gets refunded the difference (so 4).
We will run your tests against our hopefully-correct implementations, for which we expect your tests to pass. We will also run your tests against our purposefully-incorrect implementations, for which we expect some of your tests to fail and the others to pass.
We want you to create a simple UI with 4 pages, or 1 page with 4 tabs if you prefer. You do not need to make it look fancy or add list pagination. But it has to be functional. It is ok if it requires copy / paste of addresses and secrets from the human user.
You can use the framework of your choice, and it is good manner to add some instructions on how to make it run within the VM. If there is any NodeJs package that you would like to use make sure they appear in package.json
and you committed it too.
So:
- a page for the deployed
Regulator
's owner, which allows it to:- set vehicle types.
- create a new
TollBoothOperator
. - no need to make it possible to change the regulator on the individual
TollBoothOperator
s.
- a page for the
TollBoothOperator
's owner, which allows it to:- add toll booths.
- add base route prices.
- set multipliers.
- no need to make it possible to change owners of the contract.
- no need to make it possible to pause the contract.
- no need to make it possible to remove toll booths.
- no need to make it possible to change the required deposit.
- no need to make it possible to clear one pending payment.
- a page for individual vehicles, which allows it to:
- see its basic Ether balance.
- make an entry deposit.
- see its history of entry / exit.
- no need to see its pending payments.
- a page for individual toll booths, which allows it to:
- report a vehicle exit.
- be informed on the status of the refund or of the pending payment of the vehicle reported above. Typically a row in a table stating normal exit or pending payment.
- no need to see its history of entry / exit.
The way we will use your GUI is as follows:
- go into the exam VM, clone your project there and run
npm install
. - launch
ganache-cli
and copy the 10 addresses it creates. Typically we will use:- 1 for a toll booth operator owner
- 1 for a vehicle
- 2 for booths
- run
truffle migrate
from your folder - run
npm run dev
as the scaffolding is already configured to serve on0.0.0.0:8000
- if you choose to not use the default webpack configuration, make sure you document it, and have a single
npm
command that enables us to serve your GUI on0.0.0.0:8000
- if you choose to not use the default webpack configuration, make sure you document it, and have a single
- open the browser without Metamask
This is not a gotcha exercise where a minute error makes you lose huge points. We want to see you at your best so let us know of any access issues or misunderstanding.
If your tests run out of gas too easily, launch Ganache like so:
$ ganache-cli --gasLimit 15000000 --allowUnlimitedContractSize --host 0.0.0.0
- Did you run your code in the exam VM specific to your cohort? If you are not sure what VM this is, you probably did not.
- Did you run with the global
truffle
on the exam VM, without changing the Solidity compiler version? - Did you have 0 errors when running the tests provided? We run many more tests on your code so you should at least aim for a 100% pass on those we gave you.
- Did you include
scenarios.js
?- Did you spell it correctly?
- Remember that we test your scenarios with our Solidity implementations.
- Did you leave the Solidity interfaces and mocks unchanged?
- Did you copy all necessary
*.sol
files incontracts/
, and are not importing extra files from exotic paths? - Did you clone your repo again elsewhere and confirmed that
package.json
mentions all that is needed? - Is your GUI served from
0.0.0.0:8000
and not127.0.0.1:8000
?