sword_smith

FA1.2 example in LIGO

Aug 3rd, 2020
383
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.40 KB | None | 0 0
  1. // Inspired by
  2. // https://ide.ligolang.org/p/4n70T9Z_iDwy6hH2Y0b3Pw linked to from
  3. // https://assets.tqtezos.com/docs/token-contracts/fa12/2-fa12-ligo/
  4. // This is an implementation of the FA1.2 specification in PascaLIGO
  5. // WARNING: this function might be susceptible to a Transfer/Approve attack. For more information, see:
  6. // https://github.com/ecadlabs/token-contract-example/issues/6
  7.  
  8. type amt is nat;
  9.  
  10. type account is record
  11. balance : amt;
  12. allowances: map(address, amt);
  13. end
  14.  
  15. type action is
  16. | Transfer of (address * address * amt)
  17. | Approve of (address * amt)
  18. | GetAllowance of (address * address * contract(amt))
  19. | GetBalance of (address * contract(amt))
  20. | GetTotalSupply of (unit * contract(amt))
  21. | Mint of amt
  22. | Burn of amt
  23.  
  24. type contract_storage is record
  25. ledger: big_map(address, account);
  26. owner: address;
  27. totalSupply: amt;
  28. end
  29.  
  30. function isAllowed ( const spender : address ; const value : amt ; var s : contract_storage) : bool is
  31. begin
  32. var allowed: bool := False;
  33. if Tezos.sender =/= Tezos.source then block {
  34. const src: account = case s.ledger[spender] of
  35. Some (acc) -> acc
  36. | None -> (failwith("NoAccount"): account)
  37. end;
  38. const allowanceAmount: amt = case src.allowances[Tezos.sender] of
  39. Some (allowance) -> allowance
  40. | None -> (failwith("NoAllowance"): amt)
  41. end;
  42. allowed := allowanceAmount >= value;
  43. };
  44. else allowed := True;
  45. end with allowed
  46.  
  47. function burn ( const value: amt ; var s : contract_storage ) : contract_storage is
  48. begin
  49. // We could let everyone burn as they can only burn their own tokens
  50. if Tezos.sender =/= s.owner then failwith("Only owner can burn");
  51. else skip;
  52.  
  53. // Verify that caller has the sufficient balance to burn this amount
  54. // This *should* thrown if owner is not present in ledger, but get_force is deprecated
  55. // so we should probably use something else
  56. var ownerAccount := get_force(s.owner, s.ledger);
  57. if ownerAccount.balance < value then failwith("Insufficient balance to burn this amount");
  58. else skip;
  59.  
  60. ownerAccount.balance := abs(ownerAccount.balance - value);
  61. s.ledger[s.owner] := ownerAccount;
  62. s.totalSupply := abs(s.totalSupply - value);
  63.  
  64. end with s
  65.  
  66. function mint ( const value : amt ; var s : contract_storage ) : contract_storage is
  67. begin
  68. if Tezos.sender =/= s.owner then failwith("Only owner can mint");
  69. else skip;
  70.  
  71. var ownerAccount: account := record
  72. balance = 0n;
  73. allowances = (map end : map(address, amt));
  74. end;
  75. case s.ledger[s.owner] of
  76. Some (acc) -> ownerAccount := acc
  77. | None -> skip
  78. end;
  79.  
  80. // For some reason I couldn't get this to work with `abs`
  81. // Do I need to write back the value to s?
  82. ownerAccount.balance := ownerAccount.balance + value;
  83. s.ledger[s.owner] := ownerAccount;
  84. s.totalSupply := s.totalSupply + value;
  85.  
  86. end with s
  87.  
  88. // Transfer a specific amount of tokens from the accountFrom address to a destination address
  89. // Pre conditions:
  90. // The sender address is the account owner or is allowed to spend x in the name of accountFrom
  91. // The accountFrom account has a balance higher than amount
  92. // Post conditions:
  93. // The balance of accountFrom is decreased by amount
  94. // The balance of destination is increased by amount
  95. function transfer (const accountFrom : address ; const destination : address ; const value : amt ; var s : contract_storage) : contract_storage is
  96. begin
  97. // If accountFrom = destination transfer is not necessary
  98. if accountFrom = destination then skip;
  99. else block {
  100. // Is sender allowed to spend value in the name of source
  101. //case isAllowed(accountFrom, value, s) of
  102. //| False -> failwith ("Sender not allowed to spend token from source")
  103. //| True -> skip
  104. //end;
  105. const allowed = isAllowed(accountFrom, value, s);
  106. if allowed then skip;
  107. else failwith ("Sender not allowed to spend token from source");
  108.  
  109. // Fetch src account
  110. const src: account = case s.ledger[accountFrom] of
  111. Some (acc) -> acc
  112. | None -> (failwith("NoAccount"): account)
  113. end;
  114.  
  115. // Check that the source can spend that much
  116. if value > src.balance
  117. then failwith ("Source balance is too low");
  118. else skip;
  119.  
  120. // Update the source balance
  121. // Using the abs function to convert int to nat
  122. src.balance := abs(src.balance - value);
  123.  
  124. s.ledger[accountFrom] := src;
  125.  
  126. // Fetch dst account or add empty dst account to ledger
  127. var dst: account := record
  128. balance = 0n;
  129. allowances = (map end : map(address, amt));
  130. end;
  131. case s.ledger[destination] of
  132. | None -> skip
  133. | Some(n) -> dst := n
  134. end;
  135.  
  136. // Update the destination balance
  137. dst.balance := dst.balance + value;
  138.  
  139. // Decrease the allowance amount if necessary
  140. if accountFrom =/= sender then block {
  141. const allowanceAmount: amt = case src.allowances[Tezos.sender] of
  142. Some (allowance) -> allowance
  143. | None -> (failwith("NoAllowance"): amt)
  144. end;
  145. if allowanceAmount - value < 0 then failwith ("Allowance amount cannot be negative");
  146. else src.allowances[Tezos.sender] := abs(allowanceAmount - value);
  147. } else skip;
  148.  
  149. s.ledger[destination] := dst;
  150. }
  151. end with s
  152.  
  153. // Approve an amount to be spent by another address in the name of the sender
  154. // Pre conditions:
  155. // The spender account is not the sender account
  156. // Post conditions:
  157. // The allowance of spender in the name of sender is value
  158. function approve (const spender : address ; const value : amt ; var s : contract_storage) : contract_storage is
  159. begin
  160. // If sender is the spender approving is not necessary
  161. if Tezos.sender = spender then skip;
  162. else block {
  163. const src: account = case s.ledger[Tezos.sender] of
  164. Some (acc) -> acc
  165. | None -> (failwith("NoAccount"): account)
  166. end;
  167. src.allowances[spender] := value;
  168. s.ledger[Tezos.sender] := src; // Not sure if this last step is necessary
  169. }
  170. end with s
  171.  
  172. // Note that the following three view functions are intended for contract-2-contract interaction,
  173. // they are not like Ethereum's view functions which can run without writing to the blockchain.
  174. // If you want to read a balance or another value from a deployed contract, you should read
  175. // directly from memory.
  176.  
  177. // View function that forwards the allowance amount of spender in the name of tokenOwner to a contract
  178. // Pre conditions:
  179. // None
  180. // Post conditions:
  181. // The state is unchanged
  182. function getAllowance (const owner : address ; const spender : address ; const contr : contract(amt) ; var s : contract_storage) : list(operation) is
  183. begin
  184. const src: account = case s.ledger[owner] of
  185. Some (acc) -> acc
  186. | None -> (failwith("NoAccount"): account)
  187. end;
  188. const destAllowance: amt = case src.allowances[spender] of
  189. Some (allowance) -> allowance
  190. | None -> (failwith("NoAllowance"): amt)
  191. end;
  192. end with list [transaction(destAllowance, 0tz, contr)]
  193.  
  194. // View function that forwards the balance of source to a contract
  195. // Pre conditions:
  196. // None
  197. // Post conditions:
  198. // The state is unchanged
  199. function getBalance (const src : address ; const contr : contract(amt) ; var s : contract_storage) : list(operation) is
  200. begin
  201. const src: account = case s.ledger[src] of
  202. Some (acc) -> acc
  203. | None -> (failwith("NoAccount"): account)
  204. end;
  205. end with list [transaction(src.balance, 0tz, contr)]
  206.  
  207. // View function that forwards the totalSupply to a contract
  208. // Pre conditions:
  209. // None
  210. // Post conditions:
  211. // The state is unchanged
  212. function getTotalSupply (const contr : contract(amt) ; var s : contract_storage) : list(operation) is
  213. list [transaction(s.totalSupply, 0tz, contr)]
  214.  
  215. function main (const p : action ; const s : contract_storage) :
  216. (list(operation) * contract_storage) is
  217. block {
  218. // Reject any transaction that try to transfer token to this contract
  219. if amount =/= 0tz then failwith ("This contract does not accept tezi deposits");
  220. else skip;
  221. } with case p of
  222. | Transfer(n) -> ((nil : list(operation)), transfer(n.0, n.1, n.2, s))
  223. | Approve(n) -> ((nil : list(operation)), approve(n.0, n.1, s))
  224. | Mint(n) -> ((nil : list(operation)), mint(n, s))
  225. | Burn(n) -> ((nil : list(operation)), burn(n, s))
  226. | GetAllowance(n) -> (getAllowance(n.0, n.1, n.2, s), s)
  227. | GetBalance(n) -> (getBalance(n.0, n.1, s), s)
  228. | GetTotalSupply(n) -> (getTotalSupply(n.1, s), s)
  229. end
  230.  
Add Comment
Please, Sign In to add comment