Please audit the contract code for me

Please check the contract code for me to see if there is a security breach

Because many contracts on the ETH have loopholes, at the request of the user, under the premise of open source contract code, the user suggested that the official team to help review, so that the user can rest assured to use.

The following is the contract I wrote for exchanging pledged COINS. In short, the user can get the token by locking up AE and choosing mining time
Below are the pledge rules, please help me to look at, to prevent the creation of loopholes

The user calls three methods: lock,unlock, and continue_lock

Lock: The user locks AE to get the token for the specified rule
Unlock: The user unlock their AE by going to the specified height
Continue_lock: Users renew their tokens to a high degree by pledging them

Below is the contract code

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,
      //The amount of mine
      token_number: int,
      //Unlock height
      unlock_height: int,
      //Continue height
      continue_height: int,
      //The height of the mine
      day: int}

  //Mined user map
  record map_accounts = {
    heights: map(int, account), count: 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
    accounts: map(address, map_accounts),
    //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
    accounts = {},
    //Initialize the number of digits, 18 digits after the decimal point
    decimals = 1000000000000000000
    }

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

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


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

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

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

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

  //All users mined
  stateful entrypoint getAccounts() : map(address, map_accounts) =
    state.accounts

  //Current mine user record
  entrypoint getAccountsHeight(addr : address) : map_accounts =
    switch(Map.lookup(addr, state.accounts))
      Some(map_accounts) => map_accounts
      None => {heights = {},count = 0}

  //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

  //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


  private function getMapAccount(addr: address): map_accounts =
    switch(Map.lookup(addr, state.accounts))
      Some(map_accounts) => map_accounts
      None => {heights = {},count = 0}

  private function getMapHeightAccount(map_accounts: map_accounts , block_height : int): bool =
    switch(Map.lookup(block_height, map_accounts.heights))
      Some(account) => true
      None => false

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

    //Minimum support 1ae
    if(Call.value < 100 * state.decimals)
      require(2 == 1, "amount low ")

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

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

    //Get the height when unlocked, used as the key , The height at which the mine may continue
    let block_height_continue = (Chain.block_height + 480 * day)

    //The current height has been pledged
    if(getMapHeightAccount(getMapAccount(Call.caller),block_height))
      require(2 == 1,"The current height has been pledged")

    // 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)

    //Get the token that is finally given
    let token_number = getMineOutputCount(getBoxTokenNumber(),ledge_count) / 1000

    //Send Caller Token
    state.token.transfer(Call.caller, token_number)

    //Send Team
    state.token.transfer(ak_2Xu6d6W4UJBWyvBVJQRHASbQHQ1vjBA7d1XUeY8SwwgzssZVHK, token_number * 10 / 100)

    //Send Developer
    state.token.transfer(ak_2MHJv6JcdcfpNvu4wRDZXWzq8QSxGbhUfhMLR7vUPzRFYsDFw6, token_number * 5 / 100)

    //Generate account
    let account = {account = Call.caller, number = Call.value, token_number = token_number, unlock_height = block_height,continue_height = block_height_continue, day = day}

    //get or create map accounts
    let map_accounts = getMapAccount(Call.caller)

    //set data
    let map_accounts = map_accounts{heights[block_height] = account , count = map_accounts.count + Call.value}

    //storage
    put( state{ accounts [Call.caller] = map_accounts})

    token_number


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

    //The current height has been pledged
    if(!getMapHeightAccount(getMapAccount(Call.caller),height))
      require(2 == 1,"The current height does not exist to unlock")

    //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    if(state.accounts[Call.caller].heights[height].unlock_height > Chain.block_height)
      require(2 == 1,"The current height is less than the unlock height")


    if(state.accounts[Call.caller].heights[height].account != Call.caller)
      require(2 == 1,"Account error")

    let ae_count = state.accounts[Call.caller].heights[height].number

    Chain.spend(Call.caller, state.accounts[Call.caller].heights[height].number)

    //get or create map accounts
    let map_accounts = getMapAccount(Call.caller)


    //set data
    let map_accounts = map_accounts{heights = Map.delete(height, map_accounts.heights) , count = map_accounts.count - state.accounts[Call.caller].heights[height].number}

    // //Clear the data for the current height
    put( state{ accounts [Call.caller] = map_accounts})

    ae_count

  //Cntinue repayment
  stateful entrypoint continue_lock(height: int, day : int) =

      //The height has not reached to give a hint !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    if (height > Chain.block_height)
      require(2 == 1,"Height Error")

    if(!getMapHeightAccount(getMapAccount(Call.caller),height))
      require(2 == 1,"The current height does not exist to unlock")

    //Cntinue repayment
    if(state.accounts[Call.caller].heights[height].continue_height > Chain.block_height)
      require(2 == 1,"The current height is less than the continue lock height")

    if(state.accounts[Call.caller].heights[height].account != Call.caller)
      require(2 == 1,"account error")


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

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

    if(getMapHeightAccount(getMapAccount(Call.caller),block_height))
        require(2 == 1,"The current height already exists. Please wait for a few minutes")


    //Get the height when unlocked, used as the key , The height at which the mine may continue
    let block_height_continue = (Chain.block_height + 480 * day)

    //Calculate days
    let day_count = getDayCount(day, state.accounts[Call.caller].heights[height].number)

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

    //Get the token that is finally given
    let token_number = getMineOutputCount(getBoxTokenNumber(),ledge_count) / 1000

    //Send token
    state.token.transfer(Call.caller, token_number)

    //Send Team
    state.token.transfer(ak_2Xu6d6W4UJBWyvBVJQRHASbQHQ1vjBA7d1XUeY8SwwgzssZVHK, token_number * 10 / 100)

    //Send Developer
    state.token.transfer(ak_2MHJv6JcdcfpNvu4wRDZXWzq8QSxGbhUfhMLR7vUPzRFYsDFw6, token_number * 5 / 100)

    //Generate account
    let account = {account = Call.caller, number = state.accounts[Call.caller].heights[height].number, token_number = token_number, unlock_height = block_height,continue_height = block_height_continue, day = day}

    //get or create map accounts
    let map_accounts = getMapAccount(Call.caller)

    //delete old height data
    let heights = Map.delete(height, map_accounts.heights)

    //set new heights
    let map_accounts = map_accounts{heights = heights}

    //set data
    let map_accounts = map_accounts{heights[block_height] = account}

    //storage
    put( state{ accounts [Call.caller] = map_accounts})

    token_number

Here are some of my concerns

  1. If there is a loophole in the contract, how should it be fixed?
  2. Is there any situation that AE can never unlock the contract?
  3. Does user A take out User B’s AE when changing the contract?
1 Like

Can you provide your automated test suite to see what cases are already covered?

Hi, I don’t know much about automation, right? What is he?

Using aeproject for example, as also proposed in your last thread.

2 Likes

I’m using AEStudio,
The process works like this,

  1. Create a contract address of AEX9 and issue a certain number of tokens
  2. Create a contract (let’s call it defi) with our AEX9 contract address in the above code, because we’re going to use it,
  3. Transfer all tokens to DEFI contract in AEX9
  4. Tom (user) calls the DEFI contract’s lock method to lock and earn token AEX9
  5. When the specified number of days (and height) is reached, call unlock to retrieve AE, or continue_lock to continue to lock and reclaim AEX9’s token

I don’t know how I describe it, do you understand? It’s really complicated.

Awesome work, keep it up.