sword_smith icon

FA1.2 example in LIGO

sword_smith | PRO | 08/03/20 06:44:31 AM UTC | 0 ⭐ | 684 👁️ | Never ⏰ | []
text |

8.4 KB

|

None

|

0 👍

/

0 👎

// Inspired by
// https://ide.ligolang.org/p/4n70T9Z_iDwy6hH2Y0b3Pw linked to from
// https://assets.tqtezos.com/docs/token-contracts/fa12/2-fa12-ligo/
// This is an implementation of the FA1.2 specification in PascaLIGO
// WARNING: this function might be susceptible to a Transfer/Approve attack. For more information, see:
// https://github.com/ecadlabs/token-contract-example/issues/6
 type amt is nat;
 type account is record
    balance : amt;
    allowances: map(address, amt);
end
 type action is
| Transfer of (address * address * amt)
| Approve of (address * amt)
| GetAllowance of (address * address * contract(amt))
| GetBalance of (address * contract(amt))
| GetTotalSupply of (unit * contract(amt))
| Mint of amt
| Burn of amt
 type contract_storage is record
  ledger: big_map(address, account);
  owner: address;
  totalSupply: amt;
end
 function isAllowed ( const spender : address ; const value : amt ; var s : contract_storage) : bool is
  begin
    var allowed: bool := False;
    if Tezos.sender =/= Tezos.source then block {
      const src: account = case s.ledger[spender] of
        Some (acc) -> acc
        | None -> (failwith("NoAccount"): account)
      end;
      const allowanceAmount: amt = case src.allowances[Tezos.sender] of
        Some (allowance) -> allowance
        | None -> (failwith("NoAllowance"): amt)
      end;
      allowed := allowanceAmount >= value;
    };
    else allowed := True;
  end with allowed
 function burn ( const value: amt ; var s : contract_storage ) : contract_storage is
 begin
  // We could let everyone burn as they can only burn their own tokens
  if Tezos.sender =/= s.owner then failwith("Only owner can burn");
  else skip;
   // Verify that caller has the sufficient balance to burn this amount
  // This *should* thrown if owner is not present in ledger, but get_force is deprecated
  // so we should probably use something else
  var ownerAccount := get_force(s.owner, s.ledger);
  if ownerAccount.balance < value then failwith("Insufficient balance to burn this amount");
  else skip;
   ownerAccount.balance := abs(ownerAccount.balance - value);
  s.ledger[s.owner] := ownerAccount;
  s.totalSupply := abs(s.totalSupply - value);
  end with s
 function mint ( const value : amt ; var s : contract_storage ) : contract_storage is
 begin
  if Tezos.sender =/= s.owner then failwith("Only owner can mint");
  else skip;
   var ownerAccount: account := record
      balance = 0n;
      allowances = (map end : map(address, amt));
  end;
  case s.ledger[s.owner] of
      Some (acc) -> ownerAccount := acc
      | None -> skip
  end;
   // For some reason I couldn't get this to work with `abs`
  // Do I need to write back the value to s?
  ownerAccount.balance := ownerAccount.balance + value;
  s.ledger[s.owner] := ownerAccount;
  s.totalSupply := s.totalSupply + value;
  end with s
 // Transfer a specific amount of tokens from the accountFrom address to a destination address
// Pre conditions:
//  The sender address is the account owner or is allowed to spend x in the name of accountFrom
//  The accountFrom account has a balance higher than amount
// Post conditions:
//  The balance of accountFrom is decreased by amount
//  The balance of destination is increased by amount
function transfer (const accountFrom : address ; const destination : address ; const value : amt ; var s : contract_storage) : contract_storage is
 begin
  // If accountFrom = destination transfer is not necessary
  if accountFrom = destination then skip;
  else block {
    // Is sender allowed to spend value in the name of source
    //case isAllowed(accountFrom, value, s) of
    //| False -> failwith ("Sender not allowed to spend token from source")
    //| True -> skip
    //end;
    const allowed = isAllowed(accountFrom, value, s);
    if allowed then skip;
    else failwith ("Sender not allowed to spend token from source");
     // Fetch src account
    const src: account = case s.ledger[accountFrom] of
       Some (acc) -> acc
      | None -> (failwith("NoAccount"): account)
    end;
     // Check that the source can spend that much
    if value > src.balance
    then failwith ("Source balance is too low");
    else skip;
     // Update the source balance
    // Using the abs function to convert int to nat
    src.balance := abs(src.balance - value);
     s.ledger[accountFrom] := src;
     // Fetch dst account or add empty dst account to ledger
    var dst: account := record
        balance = 0n;
        allowances = (map end : map(address, amt));
    end;
    case s.ledger[destination] of
      | None -> skip
      | Some(n) -> dst := n
    end;
     // Update the destination balance
    dst.balance := dst.balance + value;
     // Decrease the allowance amount if necessary
    if accountFrom =/= sender then block {
        const allowanceAmount: amt = case src.allowances[Tezos.sender] of
          Some (allowance) -> allowance
          | None -> (failwith("NoAllowance"): amt)
        end;
        if allowanceAmount - value < 0 then failwith ("Allowance amount cannot be negative");
        else src.allowances[Tezos.sender] := abs(allowanceAmount - value);
    } else skip;
     s.ledger[destination] := dst;
  }
 end with s
 // Approve an amount to be spent by another address in the name of the sender
// Pre conditions:
//  The spender account is not the sender account
// Post conditions:
//  The allowance of spender in the name of sender is value
function approve (const spender : address ; const value : amt ; var s : contract_storage) : contract_storage is
 begin
  // If sender is the spender approving is not necessary
  if Tezos.sender = spender then skip;
  else block {
    const src: account = case s.ledger[Tezos.sender] of
       Some (acc) -> acc
      | None -> (failwith("NoAccount"): account)
    end;
    src.allowances[spender] := value;
    s.ledger[Tezos.sender] := src; // Not sure if this last step is necessary
  }
 end with s
 // Note that the following three view functions are intended for contract-2-contract interaction,
// they are not like Ethereum's view functions which can run without writing to the blockchain.
// If you want to read a balance or another value from a deployed contract, you should read
// directly from memory.
 // View function that forwards the allowance amount of spender in the name of tokenOwner to a contract
// Pre conditions:
//  None
// Post conditions:
//  The state is unchanged
function getAllowance (const owner : address ; const spender : address ; const contr : contract(amt) ; var s : contract_storage) : list(operation) is
 begin
  const src: account = case s.ledger[owner] of
    Some (acc) -> acc
    | None -> (failwith("NoAccount"): account)
  end;
  const destAllowance: amt = case src.allowances[spender] of
    Some (allowance) -> allowance
    | None -> (failwith("NoAllowance"): amt)
  end;
 end with list [transaction(destAllowance, 0tz, contr)]
 // View function that forwards the balance of source to a contract
// Pre conditions:
//  None
// Post conditions:
//  The state is unchanged
function getBalance (const src : address ; const contr : contract(amt) ; var s : contract_storage) : list(operation) is
 begin
  const src: account = case s.ledger[src] of
    Some (acc) -> acc
    | None -> (failwith("NoAccount"): account)
  end;
 end with list [transaction(src.balance, 0tz, contr)]
 // View function that forwards the totalSupply to a contract
// Pre conditions:
//  None
// Post conditions:
//  The state is unchanged
function getTotalSupply (const contr : contract(amt) ; var s : contract_storage) : list(operation) is
  list [transaction(s.totalSupply, 0tz, contr)]
 function main (const p : action ; const s : contract_storage) :
  (list(operation) * contract_storage) is
 block {
   // Reject any transaction that try to transfer token to this contract
   if amount =/= 0tz then failwith ("This contract does not accept tezi deposits");
   else skip;
  } with case p of
  | Transfer(n) -> ((nil : list(operation)), transfer(n.0, n.1, n.2, s))
  | Approve(n) -> ((nil : list(operation)), approve(n.0, n.1, s))
  | Mint(n) -> ((nil : list(operation)), mint(n, s))
  | Burn(n) -> ((nil : list(operation)), burn(n, s))
  | GetAllowance(n) -> (getAllowance(n.0, n.1, n.2, s), s)
  | GetBalance(n) -> (getBalance(n.0, n.1, s), s)
  | GetTotalSupply(n) -> (getTotalSupply(n.1, s), s)
  end
 

Comments