# Common Patterns
In this chapter, we will cover the common patterns of Vite smart contract development with an example.
# A Well-Structured Vite Smart Contract
The following contract comes from a collateral vault that accepts Vite coins, and in turn, mints a stablecoin VUSD.
The contract inherits Vault.solpp
and openzeppelin/access/Ownable.sol
. As an abstract contract, Vault.solpp
defines the base functions and the data structure of the vault. Ownable.sol
is a standard OpenZeppelin contract of Solidity, which is imported into the collateral contract without change. The contract also declares two interface fields IOracle.solpp
and ITokenMinter.solpp
, corresponding to a price oracle and a token minter - they will be instantiated during deployment. When the contract is in actual use, the token minter should be the Vite's token issuance contract, and the oracle is assigned with the contract address of TokenPriceOracle.solpp
. We simply replace with MockOracle.solpp
and MockTokenMinter.solpp
in our example for demo purpose and testing.
MockOracle.solpp
returns a fixed price, and MockTokenMinter.solpp
maintains an amount of pre-minted VUSD tokens and sends to certain address when it is called.
The contract's directory structure is as below.
Contract directory structure
# Using Solidity Code
Most Solidity code can be directly used in Solidity++ without any change. This also applies to most contracts in OpenZeppelin library except the "ERCxxx" token contracts in Ethereum. In the example, CollateralVault extends Ownable.sol
so that the onlyOwner
modifier can be used in the contract. Ownable.sol
is an standard OpenZeppelin contract written in Solidity.
Vite implements a native token issuance model. Issuing or minting a new token doesn't need a "Token Contract" as in Ethereum.
# How to Transfer Tokens
In the example, the contract sends an amount of amountToWithdraw
VITE tokens to msg.sender
, which is the caller of the withdraw
function. msg.sender
must be payable
in order to receive funds.
Note: The syntax of sending token to another address is
address.transfer(_tti, _amount)
# Using Implicit Receive Function
MockedTokenMinter.solpp
has defined a default (implicit) receive function, which allows others send tokens to the contract. Note that the receive function doesn't have any parameters and must be declared as payable
.
Note: Solidity++ contract doesn't have a default receive function. The contract cannot receive transactions - any token sent to the contract will be returned. Always add the receive function if your contract needs to receive tokens.
# How to Call Another Contract
CollateralVault
calls the price oracle contract to obtain the current price. For example, await oracle.getLatestPrice()
calls the getLatestPrice()
function on the oracle and returns the price. The await
operator indicates the call is synchronous. The execution will pause until the price is returned from the oracle.
Await operator and Solidity
The await
operator is introduced in Solidity++ v0.8.1. It is a powerful tool that makes synchronous-like calls on Vite possible. Basically, it behaves similarly to a function call in Ethereum, and gives the same execution result. However, due to the asynchronous nature of Vite, in some execution context it might give unexpected results (compared to that in Ethereum). We will explain these pitfalls in the next chapter.
Before the await
operator is introduced, all the contract call on Vite is asynchronous. They will return immediately and the Vite VM will start execute the next line of code. In the example, if we remove await
, oracle.getLatestPrice()
will become an async call, and the next line return _depositAmount * price * 10**VUSD_DECIMALS / 10**(VITE_DECIMALS + decimals)
will start to run with an invalid price (apparently not the expected behavior for our example).
Note: The syntax of calling another contract is
contract.f(_params)
. If you want to make it synchronous, simply addingawait
ahead, i.e.await contract.f(_params)
.
# How to Transfer Tokens when Calling Another Contract
After received VUSD repayment, the withdraw
function burns the tokens by calling await tm.Burn{token: STABLE_TOKEN, value: _repayAmount}()
. This calls the Burn
function on TokenMinter contract and sends the tokens for burning.
Note: The syntax is
contract.f{token: _vitetoken, value: _amount}(_params)
. The function must bepayable
.
# Using Libraries(OpenZeppelin)
Using OpenZeppelin library is also possible in Solidity++. Let us modify the CollateralVault.solpp
contract to use the library SafeMath.sol
.
Same as what need to be done in Solidity, We add an new import and declare to use the library in the contract.
We modified getMintAmount(uint256 _depositAmount)
by replacing the original math operations with functions provided in the library.