Need help with encodeCalldata (Sophia compiler)

Hey guys,

I am just trying to deploy and execute our smart contract. Currently I am struggling with calling encodeCalldata successfully.

This is our contract:

contract PaymentSplitter =
   record state =
      {  owner: address,
         recipientConditions: map(address, int) } // map of recipients with percentage to receive (value between 1 and 100)

   // CONTRACT EVENTS
   datatype event = AddingInitialRecipients()
      | RecipientAdded(indexed address, indexed int)
      | AddressUpdated(indexed address, indexed address)
      | UpdatingAllRecipients()
      | PaymentReceivedAndSplitted(indexed address, indexed int, indexed int)

   // CONSTRUCTOR
   public stateful function init(recipientConditions': map(address, int)) : state =
      require(sumWeights(recipientConditions') == 100, "sum of weights needs to be 100")
      Chain.event(AddingInitialRecipients)
      {  owner = Call.caller,
         recipientConditions = recipientConditions'}

   // READ ONLY FUNCTIONS

   public function getOwner() : address =
      state.owner

   public function getRecipientsCount() : int =
      Map.size(state.recipientConditions)

   public function isRecipient(who': address) : bool =
      Map.member(who', state.recipientConditions)

   public function getWeight(who': address) : int =
      Map.lookup_default(who', state.recipientConditions, 0)

   // PAY-AND-SPLIT FUNCTION
   public function payAndSplit() =
      require(Contract.balance > 0, "contract didn't receive any payment")
      let recipientConditions: list((address, int)) = Map.to_list(state.recipientConditions)
      split(recipientConditions, Contract.balance)
      Chain.event(PaymentReceivedAndSplitted(Call.caller, Call.value, Contract.balance))

   // STATEFUL FUNCTIONS

   public stateful function transferOwnership(newOwner': address) =
      onlyOwner()
      put(state{owner = newOwner'})

   public stateful function updateAddress(oldAddress': address, newAddress': address) =
      onlyOwnerOrRecipient(oldAddress')
      let weight: int = state.recipientConditions[oldAddress']
      put(state{recipientConditions @ rc = Map.delete(oldAddress', rc)}) // remove old address
      put(state{recipientConditions[newAddress'] = weight}) // add new address
      Chain.event(AddressUpdated(oldAddress', newAddress'))
   
   public stateful function updateRecipientConditions(recipients': map(address, int)) =
      onlyOwner()
      Chain.event(UpdatingAllRecipients)
      require(sumWeights(recipients') == 100, "sum of weights needs to be 100")
      put(state{recipientConditions = recipients'})
      fireRecipientAddedEvents(Map.to_list(state.recipientConditions))
   
   // PRIVATE FUNCTIONS

   private function onlyOwner() =
      require(Call.caller == state.owner, "caller must be the owner")

   private function onlyOwnerOrRecipient(recipient': address) =
      require(Call.caller == state.owner || Call.caller == recipient', "caller must be the owner or the recipient")

   private function sumWeights(recipients': map(address, int)) : int =
      let recipientList: list((address, int)) = Map.to_list(recipients')
      let intList: list(int) = map(pair_second, recipientList)
      sum(intList, (x) => x)

   private function fireRecipientAddedEvents(recipientConditions': list((address, int))) =
      switch(recipientConditions')
         [] => ()
         (recipient, weight) :: l' =>
            Chain.event(RecipientAdded(recipient, weight))

   private function split(recipientConditions': list((address, int)), totalValue: int) =
      switch(recipientConditions')
         [] => ()
         (recipient, weight) :: l' =>
            Chain.spend(recipient, totalValue / 100 * weight)
            split(l', totalValue)

   // GENERIC HELPER FUNCTIONS

   private function map(f : 'a => 'b, l : list('a)) : list('b) =
      switch(l)
         [] => []
         e :: l' => f(e) :: map(f, l')

   private function foldr(f : (('a, 'b) => 'b), z: 'b, l : list('a)) : 'b =
      switch(l)
         [] => z
         e :: l' => f(e, foldr(f, z, l'))

   private function sum(l : list('a), f : 'a => int) : int =
      foldr((x, y) => x + y, 0, map(f, l))

   private function pair_second(tuple) =
      switch(tuple)
         (_, e) => e

   private function require(b: bool, err: string) =
      if(!b) abort(err)

Currently I am trying to encode the calldata for the init function in order to deploy the contract. What I am doing is calling the API with the following parameters:

  • source: String that contains the code above
  • function: init
  • arguments: a list that contains the following string: [{'ak_YZj5WoWTBvga5MBXHa49SLaScbqbop2NVn5WLDsbXwS71aFrM': 40}, {'ak_ERsQrUnSN7RX3NNGRbuu9skZuBJ6cJLHwcE4FZ2XAotKoR6bo': 40}, {'ak_TtQYek584v6M2XYEKzBMPRqdat8FSUr2dqNshvN14RjvX6q8W': 20}]

Can somebody of the team please tell how the argument must look like?

1 Like
"{[ak_YZj5WoWTBvga5MBXHa49SLaScbqbop2NVn5WLDsbXwS71aFrM] = 40, 
  [ak_ERsQrUnSN7RX3NNGRbuu9skZuBJ6cJLHwcE4FZ2XAotKoR6bo] = 40,
  [ak_TtQYek584v6M2XYEKzBMPRqdat8FSUr2dqNshvN14RjvX6q8W] = 20}"
3 Likes

I’m really embarrassed right now :see_no_evil: … stupidity rules the world.

Thanks! Was able to deploy the contract now :slight_smile:

@hanssv we are thinking about providing a plugin that allows to auto-generate code to make use of smart contracts. is in æternity also an ABI json file available like it is in ethereum?

  • this would make it easier for us to “parse” through the contract functions and parameters which we need to know for code-generation

Yes, there is something that we call ACI (Aeternity Contract Interface?) it can be generated from a a contract using either the HTTP API for the compiler (aesophia_http) or the command line interface (aesophia_cli) - basically it will give you the contract interface in a handy JSON-format.

@nikita-fuchs can describe it in more detail, but I believe that it is used by the JS SDK and also by the Forgae project?!

https://github.com/aeternity/aesophia/blob/master/docs/aeso_aci.md contains an example of what it can do…

2 Likes

@nikita-fuchs can you tell me how I can create the ACI? I still haven’t figured out how to do that

Easily done with the CLI compiler (https://github.com/aeternity/aesophia_cli), or you could use the HTTP version (https://github.com/aeternity/aesophia_http):

~/Quviq/Aeternity/aesophia_cli: priv/bin/v3.2.0/aesophia_cli --create_json_aci test/contracts/identity.aes 
ACI generated successfully!

[{"contract":{"functions":[{"arguments":[{"name":"x","type":"int"}],"name":"main","returns":"int","stateful":false}],"name":"Identity","type_defs":[]}}]
1 Like

@hanssv You shouldn’t be posting in the forum while on vacation :wink:

@marc0olo here is the doc for doing it with the HTTP compiler also. https://github.com/aeternity/aesophia_http#generating-aci
Don’t forget to exit every " as \" :wink: