/ test / contracts / paysplit.aes
paysplit.aes
  1  payable contract PaymentSplitter =
  2     record state =
  3        {  owner: address,
  4           recipientConditions: map(address, int), // map of recipients with percentage to receive (value between 1 and 100)
  5           totalAmountSplitted: int }
  6  
  7     // CONTRACT EVENTS
  8     datatype event = AddingInitialRecipients()
  9        | RecipientAdded(indexed address, indexed int)
 10        | AddressUpdated(indexed address, indexed address)
 11        | UpdatingAllRecipients()
 12        | PaymentReceivedAndSplitted(indexed address, indexed int, indexed int)
 13  
 14     // CONSTRUCTOR
 15     entrypoint init(recipientConditions': map(address, int)) : state =
 16        require(sumWeights(recipientConditions') == 100, "sum of weights needs to be 100")
 17        Chain.event(AddingInitialRecipients)
 18        {  owner = Call.caller,
 19           recipientConditions = recipientConditions',
 20           totalAmountSplitted = 0}
 21  
 22     // READ ONLY FUNCTIONS
 23  
 24     entrypoint getOwner() : address =
 25        state.owner
 26  
 27     entrypoint getRecipientsCount() : int =
 28        Map.size(state.recipientConditions)
 29  
 30     entrypoint isRecipient(who': address) : bool =
 31        Map.member(who', state.recipientConditions)
 32  
 33     entrypoint getWeight(who': address) : int =
 34        Map.lookup_default(who', state.recipientConditions, 0)
 35  
 36     entrypoint getTotalAmountSplitted() : int =
 37        state.totalAmountSplitted
 38  
 39     // PAY-AND-SPLIT FUNCTION
 40     stateful payable entrypoint payAndSplit() =
 41        require(Contract.balance > 0, "contract didn't receive any payment")
 42        let recipientConditions: list(address * int) = Map.to_list(state.recipientConditions)
 43        put(state{totalAmountSplitted = Contract.balance + state.totalAmountSplitted})
 44        split(recipientConditions, Contract.balance)
 45        Chain.event(PaymentReceivedAndSplitted(Call.caller, Call.value, Contract.balance))
 46  
 47     // STATEFUL FUNCTIONS
 48  
 49     stateful entrypoint transferOwnership(newOwner': address) =
 50        onlyOwner()
 51        put(state{owner = newOwner'})
 52  
 53     stateful entrypoint updateAddress(oldAddress': address, newAddress': address) =
 54        onlyOwnerOrRecipient(oldAddress')
 55        let weight: int = state.recipientConditions[oldAddress']
 56        put(state{recipientConditions @ rc = Map.delete(oldAddress', rc)}) // remove old address
 57        put(state{recipientConditions[newAddress'] = weight}) // add new address
 58        Chain.event(AddressUpdated(oldAddress', newAddress'))
 59  
 60     stateful entrypoint updateRecipientConditions(recipients': map(address, int)) =
 61        onlyOwner()
 62        Chain.event(UpdatingAllRecipients)
 63        require(sumWeights(recipients') == 100, "sum of weights needs to be 100")
 64        put(state{recipientConditions = recipients'})
 65        fireRecipientAddedEvents(Map.to_list(state.recipientConditions))
 66  
 67     // PRIVATE FUNCTIONS
 68  
 69     function onlyOwner() =
 70        require(Call.caller == state.owner, "caller must be the owner")
 71  
 72     function onlyOwnerOrRecipient(recipient': address) =
 73        require(Call.caller == state.owner || Call.caller == recipient', "caller must be the owner or the recipient")
 74  
 75     function sumWeights(recipients': map(address, int)) : int =
 76        let recipientList: list(address * int) = Map.to_list(recipients')
 77        let intList: list(int) = map(pair_second, recipientList)
 78        sum(intList, (x) => x)
 79  
 80     function fireRecipientAddedEvents(recipientConditions': list(address * int)) =
 81        switch(recipientConditions')
 82           [] => ()
 83           (recipient, weight) :: l' =>
 84              Chain.event(RecipientAdded(recipient, weight))
 85  
 86     stateful function split(recipientConditions': list(address * int), totalValue: int) =
 87        switch(recipientConditions')
 88           [] => ()
 89           (recipient, weight) :: l' =>
 90              Chain.spend(recipient, totalValue / 100 * weight)
 91              split(l', totalValue)
 92  
 93     // GENERIC HELPER FUNCTIONS
 94  
 95     function map(f : 'a => 'b, l : list('a)) : list('b) =
 96        switch(l)
 97           [] => []
 98           e :: l' => f(e) :: map(f, l')
 99  
100     function foldr(f : (('a, 'b) => 'b), z: 'b, l : list('a)) : 'b =
101        switch(l)
102           [] => z
103           e :: l' => f(e, foldr(f, z, l'))
104  
105     function sum(l : list('a), f : 'a => int) : int =
106        foldr((x, y) => x + y, 0, map(f, l))
107  
108     function pair_second(tuple) =
109        switch(tuple)
110           (_, e) => e