# Examples of contracts

In this chapter, we will present the structure of simplified versions of a number of classical smart contracts:

The goal is for you to get a good understanding of what a smart contract is, how the storage and entry points are used. We will also introduce how and why contracts interact with each other.

Finally, this will give you an overview of what some of the most common smart contract may look like.

danger

The contracts below are simplified contracts, provided for educational purposes only. They are not meant to be implemented and used as is, as some of them may contain potential flaws.

## FA1.2 - Fungible token​

The goal of this contract is to create and manage a single fungible token.

It implements the FA1.2 standard, which makes it compatible with wallets, decentralized exchanges and other tools.

It only supports a small number of features:

• Each user can own a certain number of tokens
• Users can transfer tokens to other users
• Users can allow another contract, for example a decentralized exchange, to transfer some amount of their tokens for them.

The contract contains two main entry points:

• transfer, to transfer a number of token from one address to another
• approve, for a caller to indicate that they allow another address to transfer a number of their tokens

To be compatible with FA1.2, and so that other contacts can access to information, it also contains three entry points that have no effect other than sending information back to the caller:

• getBalance sends the number of tokens owned by a given address
• getAllowance sends the amount of tokens belonging to a certain address that another address is allowed to transfer for them
• getTotalSupply sends the total amount of tokens managed by this contract
 Storage Entry points effects totalsupply: natledger: big-mapKey:holder: addressValue:tokens: natallowance: big-mapKey:owner: addressspender: addressValue:amount: nat transfer(from: address, to: address, value:nat)Check that ledger[from].tokens ≥ valueIf the caller address is not fromCheck that allowance[from, caller] exists, with amount ≥ valueSubstract value from allowance[from, caller].amountIf allowance[from, caller].amount = 0, delete allowance[from, caller]Create entry ledger[to] with 0 tokens, if it doesn't exist.Substract value from ledger[from].tokensAdd value to ledger[to].tokensapprove(sender: address, value: nat)Create entry allowance[caller, sender] with amount 0, if it doesn't exist.Add value to allowance[caller, sender].amountgetBalance(owner: address, callback: contract)If ledger[owner] exists, set ownerBalance to ledger[owner].tokensOtherwise, set ownerBalance to 0Call callback(ownerBalance)getAllowance(owner: address, spender: address, callback: contract)If allowance[owner, spender] exists, set amount to allowance[owner, spender]Otherwise, set amount to 0Call callback(amount)getTotalSupply(callback: contract)Call callback(totasupply)

## FA2 - NFTs: Non Fungible Tokens​

The FA2 standard specifies contracts that can be of different types:

• Single fungible token
• Multiple fungible tokens
• Non fungible tokens (NFTs)

Implementing the FA2 standard allows the contract to be compatible with wallets, explorers, marketplaces, etc.

Here, we will present an implementation for NFTs. The entry points for the other types are the same, but the implementation differs.

FA2 contracts must have the following entry points:

• transfer can be called either by the owner of tokens to be transferred, or by an operator allowed to do so on their behalf.
It takes a list of transfers of different tokens from the owner, to different addresses.
• update_operator can be called by the owner of tokens to add or remove operators allowed to perform transfers for them.
It takes a list of variants, each consisting in either addding or removing an operator for a given token.
• balance_of is used to access the balance of a user for a given token.

FA2 supports a number of optional entry points to access information, but we won't provide them here.

 Storage Entry points effects ledger: big-mapKey:token_id: natValue:owner: addressmetadata: stringoperators: big-mapKey:owner: addressoperator: addresstoken_id: natValue:nothing transfer(from: address, transfers: list of [to: address, token_id: nat, amount: nat])For each item [to, token_id, amount] in transfersCheck that amount is 1Check that caller is from, or that operators[from, caller, token_id] existsCheck that ledger[token_id].owner is fromSet ledger[token_id].owner to toupdate_operator(updates: list of [type: variant, owner: address, operator: address, token_id: nat])For each item in updates of type add_operator:Check that owner is the callerCreate entry operators[owner, operator, token_id] if it doesn't exist.For each item in updates of type remove_operator:Check that owner is the callerDelete entry operators[owner, operator, token_id] if it exists.balance_of(requests: list of [owner: address, token_id: nat], callback: address)Create list results of [owner: address, token_id: nat, balance: nat]For each request in requests:If ledger[token_id].owner is owner, set balance to 1Otherwise, set balance to 0Add [owner, token_id, balance] to resultsCall callback(results)

## NFT Marketplace​

The goal of this contract is to manage sales of NFTs from one address to another. It pays a share of the selling price to the admin of the marketplace, in exchange for providing a dApp that facilitates finding and purchasing NFTs.

It provides the following entry points:

• add is called by a seller who puts their one of their NFTs on sale for a given price.
The seller must indicate which FA2 contract holds the NFT, and what the id of the NFT is within that contract.
It requires for the marketplace to have been set as an operator in the FA2 contract, for this token.
• remove can be called by a seller to remove their NFT from the marketplace, if it hasn't been sold.
• buy is to be called by a buyer who pays the set price to buy a given NFT.
The admin account of the marketplace receives a share of the selling price.
 Storage Entry points effects admin: addressfee_rate: nattokens: big-mapKey:contract: addresstoken_id: natValue:seller: addressprice: tez add(token_contract: address, token_id: nat, price: tez)Transfer token ownership to the marketplace:call token_contract.transfer(caller, [self, token_id, 1])Add entry [token_contract, token_id] to tokens with values [caller, price]remove(token_contract: address, token_id: nat)Check that caller is the seller of the token:check that tokens[token_contract, token_id].seller is callerTransfer token ownership back to seller:call token_contract.transfer(self, [caller, token_id, 1])Delete entry [token_contract, toen_id] from tokensbuy(token_contract: address, token_id: nat)Check that the token is on sale for the amount paid by the caller:check that tokens[token_contract, token_id].amount = amount_paidTransfer token ownership to the caller:call token_contract.transfer(self, [caller, token_id, 1])Set admin_fee = fee_rate * amount_paid / 100Create transaction to send admin_fee to adminCreate transaction to send amount_paid - admin_fee to [token_contract, token_id].sellerDelete entry [token_contract, toen_id] from tokens

## Escrow​

An escrow is a contract that temporarily holds funds in reserve, for example tokens paid by a buyer of a service, while their request is being processed.

Its goal is to provide trust between the parties of a transaction that can't be atomic:

• the buyer can't send the payment to the service until the request has been fulfilled.
• the service can't start working on the request without a guarantee that it will get paid.

There are a number of different types of escrow contracts. In our contract, the service to be provided is some data that needs to be sent by the service, where the escrow contract has the ability to verify the validity of the data.

For example, the request could consist in the service sending the decrypted version of some encrypted data.

Our contract has three entry points:

• send_request creates a new request with a deadline and collects the payment, that will be held in the escrow.
Along with the data, the request contains the code that will verify the validity of the answer (a lambda)
• fulfill_request is to be called later by the service.
It verifies that the request has been performed and transfers the funds to the service.
• cancel_request can be called buy the buyer if the request had not been fulfilled after the deadline.
It transfers the funds back to them.
 Storage Entry points effects requests: big-mapKey:owner: addressid: natValue:amount: tezservice: contractdata: bytesverification: lambdadeadline: datetimeanswer: option send_request(id, service, data, verification, deadline)Check that requests[caller, id] doesn't existCreate requests[caller,id] entry with amount_paid and all the parametersCreate a call to service(data, self, amount, deadline)fulfill_request(id, answer)Set request = requests[caller, id], checking that it existsCheck that verification(request.data, answer) returns trueSet requests[caller, id].answer to answerCreate transaction to send request.amount to callercancel_request(id)Set request = requests[caller,id], checking that it existsCheck that request.answer is none, meaning the request hasn't been processedCheck that the deadline has expired: now > request.deadlineCreate transaction to send request.amount to callerDelete requests[caller,id] entry

## DAO: Decentralized Autonomous Organization​

A DAO is a contract that represents an entity composed of a number of participants. It provides a way for these participants to collectively take decisions, for example on how to use tokens held in the balance of the DAO contract.

There can be all kinds of DAOs, and we will present a simple but powerful version.

Our DAO stores the addresses of all its members, a list of all the proposals, and keeps track of who voted for them.

It has the following entry points :

• propose can be called by any member to make a new proposal, in the form of a piece of code to execute (a lambda).
• vote can be called by any member to vote in favor of the request.
When the majority of members voted in favour, the proposal is executed.
• add_member adds a new member to the DAO.
It may only be called by the DAO itself, which means the call has to go through a proposal and be voted on.
• remove_member removes a member from the DAO.
It may only be called by the DAO itself.

When deployed, an initial list of members needs to be put in the storage, and typically some tez put in the balance.

 Storage Entry points effects nb_members: natmembers: big-mapKey:user: addressValue:nothingrequests: big-mapKey:id: natValue:action: lambdadeadline: datetimenb_votes: natvotes: big-mapKey:request_id: natuser: addressValue:nothing propose(id, action, deadline)Check that requests[id] doesn't existCheck that the caller is a member: members[caller] existsCreate requests[id] entry with values of action and deadline, and with nb_votes set to 0vote(id)Check that the caller is a member: members[caller] existsCheck that the deadline is not passed: now > requests[id].deadlineCheck that the caller has not voted: votes[id, caller] doensn't existRecord vote: create votes[id, caller] entryIncrement requests[id].nb_votesIf we have a majority of votes: requests[id].nb_votes * 2 > nb_membersExcecute requests[id].lambda, and the transactions it returns.Delete entry requests[id]add_member(user)Check that the caller is the DAO itself: caller == selfCreate entry members[user], checking that it doesn't already exist.Increment nb_membersremove_member(user)Check that caller is the DAO itself: caller == selfDelete entry members[user], checking that it exists.Decrement nb_members

## DeFi: Flash loan​

A Flash loan is one of the many tools of decentralized Finance (deFi).

It provides a way for a user to temporarily get access to large amounts of tez, without any collateral. This allows them to take advantage of opportunities and make a profit, without the need to have their own funds.

The idea is that the following steps can will be done in an atomic way:

• the borrower receives the requested amount from the contract
• the borrower uses the amount in a series of calls to other contracts, that allow him to make some instant profit
• the borrower then pays the requested amount plus some interest to the contract, and gets the rest of the profit.

The key aspect to understand is that all this is done atomically, which means that if any of these step fails, and if for example the borrower is not able to pay back the borrowed amount plus interest, then the whole sequence is canceled, as if it never happens. There is no risk at all for the lender contract.

This contract can even lend the same tez to multiple different people within the same block, as each loan is paid back immediately, so the tokens can be used again for another loan.

One may use a flash loan to take advantage of an arbitrage situation, for example if two different exchanges offer to buy/sell the same type of tokens, at a different price from eachother. The user can buy tokens from one exchange at a low price, then sell it to the other exchange at a higher price, making a profit.

Our contract has three entry points:

• borrow is called by the borrower, indicating how many tez they need.
The amount is transferred to the caller, then a callback he provided is executed. At the end of this entry point, we verify that this callback has repaid the loan.
• repay is to be called by this callback, once the actions that generate a profit are done. The call should come with payment of the borrowed amount, plus interest.
• check_repaid is called by the borrow entry point after the call to the callback. Indeed, borrow can't do the verification itself, since the execution of the callback is done after all the code of the entry point is executed.
 Storage Entry points effects admin: addressinterest_rate: natin_progress: booleanloan_amount: tezrepaid: booean borrow(loan_amount: tez, callback: address)Check that in_progress is falseSet in_progress to trueTransfer loan_amount to callerSet storage loan_amount to loan_amountSet repaid to falseCreate call to callbackCreate call to check_repaidrepay()Check that in_progress is trueCheck that paid_amount is more than loan_amount + interestSet repaid to truecheck_repaid()Check that repaid is truecollect(nbTez)Check that caller is adminTransfer nbTez to admin