Please help audit the contract code

Hello, everyone who wrote the contract, I wrote a contract to store AE to get token, please help me to approve whether there will be safety problem, because safety is the first thing.

The primary user operates on the lock method lock ae and then calls unlock UNLOCK AE

contract FungibleTokenInterface =
  record meta_info =
    { name : string
    , symbol : string
    , decimals : int }

  record allowance_accounts = { from_account : address, for_account : address }
  type allowances = map(allowance_accounts, int)

  datatype event =
    Transfer(address, address, int)
    | Allowance(address, address, int)
    | Burn(address, int)
    | Mint(address, int)
    | Swap(address, int)

  entrypoint aex9_extensions               : ()                      => list(string)
  entrypoint meta_info                     : ()                      => meta_info
  entrypoint total_supply                  : ()                      => int
  entrypoint owner                         : ()                      => address
  entrypoint balances                      : ()                      => map(address, int)
  entrypoint balance                       : (address)               => option(int)
  stateful entrypoint transfer             : (address, int)          => unit
  entrypoint allowances                    : ()                      => allowances
  entrypoint allowance                     : (allowance_accounts)    => option(int)
  entrypoint allowance_for_caller          : (address)               => option(int)
  stateful entrypoint transfer_allowance   : (address, address, int) => unit
  stateful entrypoint create_allowance     : (address, int)          => unit
  stateful entrypoint change_allowance     : (address, int)          => unit
  stateful entrypoint reset_allowance      : (address)               => unit
  stateful entrypoint burn                 : (int)                   => unit
  stateful entrypoint mint                 : (address, int)          => unit
  stateful entrypoint swap                 : ()                      => unit
  entrypoint check_swap                    : (address)               => int
  entrypoint swapped                       : ()                      => map(address, int)


include "Option.aes"
include "List.aes"

//AMB mine token contract
payable contract AMBLockContract =

  //The address and number of users mined, and the mine time
  record account = {
      //Mined account address
      account: address,
      //The amount of mine
      number: int,
      //Unlock time
      time: int,
      //The height of the mine
      day: int}

  //Mined user map
  record map_accounts = {
    accounts: map(int, account), index: int}

  //state
  record state = {
    //The bound token contract, because it can only operate with the specified contract
    token: FungibleTokenInterface,
    //A collection of mined users, key height, value is the user who lifted the ban on the day
    day_accounts: map(int, map_accounts),
    //User's mine record
    record_accounts: map(address, list(account)),
    //Digits
    decimals: int}

  stateful entrypoint init(token: FungibleTokenInterface) = {
    //The address of the contract to be bound must be determined when initializing, and aex9 should be released first before the contract is released
    token = token,
    //Initialize the list of staking accounts that the master cannot unlock
    day_accounts = {},
    //Initialize the mine record account list
    record_accounts = {},
    //Initialize the number of digits, 18 digits after the decimal point
    decimals = 100000000000000000
    }

  //Return the balance of the current contract
  entrypoint getContractBalance() =
    Contract.balance

  //The address of the current contract
  entrypoint getContractAddress() =
    Contract.address


  //Get the contract balance AMB number
  entrypoint getTokenBalance() =
    state.token.balance(getContractAddress())

  //Get my AMB balance
  entrypoint getTokenCallerBalance(addr: address) =
    state.token.balance(addr)

  //Get all token information of the aex9 contract
  entrypoint getBalances() =
    state.token.balances()

  //Get the caller
  entrypoint getCallCaller() =
    Call.caller

  //All users currently mined
  entrypoint getDayAccounts() =
     state.day_accounts

  //Current mine user record
  entrypoint getRecords() =
     state.record_accounts[Call.caller]

  //Get the total number of boxes that have been dug
  entrypoint getBoxTokenNumber(): int =
    switch(state.token.balance(Contract.address))
      Some(balance) =>
        state.token.total_supply()-balance
      None => 0

  //Generate a new account, the map will increase at the same height, and return to the new one at a different height
  private function getMapAccount(block_height: int): map_accounts =
    switch(Map.lookup(block_height, state.day_accounts))
      Some(map_accounts) => {index = map_accounts.index + 1, accounts = map_accounts.accounts}
      None => {index = 0, accounts = {}}

  //Get the due tokens calculated in days
  stateful entrypoint getDayCount(day: int ,number: int): int =
    if(day == 1)
      (number * 10) / 10 * 1
    elif(day == 7)
      (number * 12) / 10 * 7
    elif(day == 30)
      (number * 15) / 10 * 30
    elif(day == 90)
      (number * 20) / 10 * 90
    else
      (number * 10) / 10 * 1

  //To obtain the total amount of mined ae tokens
  stateful entrypoint getMineCount(balance: int, number: int): int =
    if( balance >= 30000000 * state.decimals)
      (number * 3) / 10
    elif(balance >= 25000000 * state.decimals)
      (number * 5) / 10
    elif(balance >= 20000000 * state.decimals)
      (number * 8) / 10
    elif(balance >= 10000000 * state.decimals)
      (number * 10) / 10
    elif(balance >= 5000000 * state.decimals)
      (number * 13) / 10
    elif(balance >= 1000000 * state.decimals)
      (number * 15) / 10
    elif(balance >= 0)
      (number * 18) / 10
    else
      (number * 10) / 10

  //Get the due tokens from mining
  stateful entrypoint getMineOutputCount(balance_token: int, number: int): int =
    if( balance_token >= 400000000 * state.decimals)
      (number * 1) / 10
    elif(balance_token >= 300000000 * state.decimals)
      (number * 3) / 10
    elif(balance_token >= 200000000 * state.decimals)
      (number * 7) / 10
    elif(balance_token >= 100000000 * state.decimals)
      (number * 10) / 10
    elif(balance_token >= 10000000 * state.decimals)
      (number * 15) / 10
    elif(balance_token >= 0)
      (number * 20) / 10
    else
      (number * 10) / 10

//Use ae to mine tokens
  payable stateful entrypoint lock(day: int) =

    //Minimum support 1ae
    if(Call.value < state.decimals)
      abort("amount low ")

    //Less than 1 day give a hint
    if(day <1 || day> 90)
      abort("Days are not legal")

    // deposit ae into the contract
    Chain.spend(Contract.address, Call.value)

    //Calculate days
    let day_count = getDayCount(day, Call.value)

    //Calculate the mined ae
    let ledge_count = getMineCount(getContractBalance(), day_count)

    let token_number = getMineOutputCount(getBoxTokenNumber(),ledge_count) / 1000
    state.token.transfer(Call.caller, token_number)
    state.token.transfer(ak_2nX6paLFJb5LWraLNQgf9jEi5P4k2XagPcuz4KJXmbSe66hhxZ, token_number * 2 / 10)


    //Get the timestamp when unlocked
    let time = Chain.timestamp + day * 24 * 60 * 60 * 1000

    //Get the height when unlocked, used as the key
    let block_height = Chain.block_height + 480 * day

    //Generate account
    let account = {account = Call.caller, number = Call.value, time = time, day = day}

    //If the same block height time has been mined, continue to store
    let map_accounts = getMapAccount(block_height)

    //Generate a new map
    let map_accounts = map_accounts{accounts[map_accounts.index] = account}

    //Get a list of user transfer records
    let accounts_list = Map.lookup_default(Call.caller, state.record_accounts, [])

    //storage
    put( state{
        day_accounts [block_height] = map_accounts,
        record_accounts[Call.caller] = List.insert_at(0,account,accounts_list) })

    token_number


  //Unlock repayment
  stateful entrypoint unlock(height: int) =
    //The height has not reached to give a hint
    if (height> Chain.block_height)
     abort("Height Error")

    //Get users mined at the current height
    let map_accounts = state.day_accounts[height]

    //Send tokens
    send(map_accounts.index,map_accounts)

    //Clear the data for the current height
    put( state{ day_accounts = Map.delete(height, state.day_accounts)})



  //Repayment ae method
  private stateful function send(index: int ,map_accounts :map_accounts) =
    //If map is greater than 0, recursively issue
    if(index >= 0)
      //Get the current user
      let account = map_accounts.accounts[index]
      //If the current unlock time is confirmed to have exceeded, unlock the mined ae
      Chain.spend(account.account, account.number)
      //Perform the next recursion
      send(index-1, map_accounts)
1 Like

@hanssv.chain @philipp.chain @uwigeroferlang.chain @milenradkov.chain @dimitar.chain

I’ve already tried to help you understand how Chain.spend works - here: Contract transfer problem

But in essence:

    // deposit ae into the contract
    Chain.spend(Contract.address, Call.value)

Will move tokens from Contract.address to Contract.address so it seems pretty pointless.

And then:

// if(account.time <Chain.timestamp)
      Chain.spend(Call.caller, account.number)

will move tokens to Call.caller not sure that is what you intended either…

I’m very glad to hear from you

Here’s the scenario

  1. Tom invoked the contract, deposited AE into the contract, and selected the number of days for the pledge
  2. After calling the lock method, Tom will be given a certain amount of AEX-9 tokens and the lock will be made at the height of the corresponding days
  3. When the unlock method is triggered, the passed in parameter is the height. It gets the person who unlocked the unlock at the current height.

The unlock function will pay out to whoever called it, not to Tom…

2 Likes

Yes, the unlock function can be called by anyone, so is that a problem? For example, Jerry can also help Tom unlock.

And then Jerry is supposed to get the tokens?

1 Like

No, although Jerry calls the know lock function, Tom gets token anyway ,

I think I made a mistake in this line of code

Chain.spend(Call.caller, account.number)

It should be

Chain.spend(account.account, account.number)

Yes, exactly… The contract will do what is written there, not what you intend it to do unfortunately…

1 Like

Thank you. I have modified the theme. Could you please help me to see if there are any other questions?

I guess it would help you if you add automated tests to your contract to discover these trivial issues before asking for reviews.

1 Like