Making an ERC20 contract
The following tutorial assumes that you already have a
constellation instance running on port
8100 and that you have installed the the Python API.
The ERC20 contract implements following functions:
totalSupply() : UInt256-- Get the total token supply.
balanceOf(owner: Address): UInt256-- Get the account balance of another account with address
transfer(to: Address, value: UInt256) : Bool-- Send
valueamount of tokens to address
transferFrom(from: Address, to: Address, value: UInt256): Bool-- Send
valueamount of tokens from address
approve(spender: Address, value: UInt256) : Bool-- Allow
spenderto withdraw from your account, multiple times, up to the
valueamount. If this function is called again it overwrites the current allowance with
allowance(owner: Address, spender: Address)-- Returns the amount which
spenderis still allowed to withdraw from
We will in the following implement most of these functions. As the Fetch.AI smart contracts do not have implicit addresses as in Ethereum, the function signatures are will be slightly different as will be seen below, but the overall functionality remains the same.
We first define the contract constructor function which is annotated with the
@init keyword. This tells the ledger that this function should be invoked upon instating the contract:
@init function createSupply(owner: Address, supply: UInt256) var supply_state = State< UInt256 >("total_supply"); supply_state.set(supply); var balance_state = State< UInt256 >(owner); balance_state.set( supply ); endfunction
supplytokens. Furthermore, for this specific contract we have made the total supply programmable such that the contract can be reused as well as to make it easy to write tests for the contract.
The ERC20 contract provides three query functions:
allowance. We will define
balanceOf in this section and dicuss
allowance in a section later on.
balanceOf are straightforward to implement. Total supply queries the
total_supply and returns it as a result:
@query function totalSupply(): UInt256 var supply_state = State< UInt256 >("total_supply"); return supply_state.get(0u64); endfunction
balanceOf, on the other hand, makes a dynamic look up based on the address of
@qeury function balanceOf(owner: Address) : UInt256 var balance_state = State< UInt256 >(owner); if(!balance_state.existed()) return UInt256(0u64); endif return balance_state.get(UInt256(0u64)); endfunction
geton the state variable and supplying a default value if the state does not exist. In the second query, we manually check whether the variable existed at the beginning of the contract call and if not, we return
0. Both are valid ways to manage a state existance.
The ERC20 contract defines three queries:
approve. We will discuss
approve in the next section. In Etch,
transferFrom are one and the same function as Etch does not have an implicitly provided sender. Rather
to are explicit function arguments and whether these addresses signed the transaction needs to be checked within the
@action function transfer(from: Address, to: Address, value: UInt256) : Bool if(!from.signedTx()) return false; endif var from_state = State< UInt256 >(from); var from_balance = from_state.get( UInt256(0u64) ); if(from_balance < value) return false; endif var to_state = State< UInt256 >(to); var to_balance = to_state.get( UInt256(0u64) ); // TODO: Polyfilling due to missing UInt256 functionality var u_from = toUInt64(from_balance); var u_to = toUInt64(to_balance); var u_amount = toUInt64(value); u_from -= u_amount; u_to += u_amount; from_balance = UInt256(u_from); to_balance = UInt256(u_to); from_state.set(from_balance); to_state.set(to_balance); return true; endfunction
The functions up until now constitute a basic token contract that allows creation of tokens and transfer between participants. The more interesting functionality is the
allowance mechanism in the ERC20 contract that gives one address the possibility of spending some amount based on the allowance details.
To create this functionality we could use the normal
State object by simply definining the object identifiers. However, a more appropriate mechanism for this purpose is the
ShardedState which ensures that the payload is assigned to an appropiate shard within the system. Implementing the
approve mechanism using the
ShardedState is relatively easy as it provides dictionary-like functionality:
@action function approve(owner: Address, spender: Address, value: UInt256) : Bool var state = ShardedState< UInt256 >(spender); state.set(owner, value); return true; endfunction
spenderaddress with the
owneraddress. However, unlike a normal dictionary, the
StateSharddoes not keep a record of which entries exists and which not. Such functionality could be added by simply adding another state variable keeping track of owners. We will see an example on a similar type of functionality in the next part of this guide.
Finally, implementing a query mechanism is equally straight foward:
@query function allowance(owner: Address, spender: Address) : UInt256 var state = ShardedState< UInt256 >(spender); return state.get(owner, UInt256(0u64)); endfunction
allowanceto be truly useful as we have not implemented any method to actually spend the allowance.
You can find the full contract here.