Skip to content

Instantly share code, notes, and snippets.

@mafellows
Last active April 25, 2024 15:18
Show Gist options
  • Save mafellows/70356b32c112e55e4f4575647756966e to your computer and use it in GitHub Desktop.
Save mafellows/70356b32c112e55e4f4575647756966e to your computer and use it in GitHub Desktop.

Automated findings report: ai-competition (bridge.sol)

Report generated by auditbase.com

This report includes two parts:

AI Report

Mismanagement of Redeemed MemID States

The function fulfill sets a MemID redeeming status to true even if it's already marked as redeemed even after asserting it was not in validateUnlock.

require(!midIsRedeemed[memid], "err_mid_redeemed");
// ...
midIsRedeemed[memid] = true;

https://github.com/fatherGoose1/ai-competition/blob/b72219537853b09c35e2497466daa8229a994f2f/src/bridge.sol#L135-L149

Confidence: 0.9

Severity: High

The midIsRedeemed mapping is utilized to keep track of whether a particular MEM ID has been used (redeemed) or not.

The validateUnlock and executeUnlock functions both set this mapping to true for the same _memid, which appears redundant and opens up potential vulnerabilities.

validateUnlock can potentially set midIsRedeemed[_memid] to true prematurely: In validateUnlock, the mapping is set to true right after the Chainlink oracle request is made, before actually validating or fulfilling the amount that is to be unlocked. This could be a design choice to prevent double spending but it also means that if the Chainlink oracle call fails or is delayed, the MEM ID will have been marked as redeemed when no tokens have actually been unlocked.

// validateUnlock function snippet
function validateUnlock(string calldata _memid) public returns (bytes32 requestId) {
    // ...
    midIsRedeemed[_memid] = true; // premature flag setting
    // ...
}

Confidence: 0.9

Severity: High (could lead to funds being locked with no way to unlock)

Insecure Validation and Execution Dependency

The contract relies on external validation (validateUnlock) that a MemID can be unlocked. If the external service is compromised, incorrect validation could lead to unauthorized unlocking of funds.

Chainlink.Request memory req = _buildOperatorRequest(jobId, this.fulfill.selector);

https://github.com/fatherGoose1/ai-competition/blob/main/src/bridge.sol#L96-L99

Confidence: 0.8

Severity: High

Code Scan

M001 - Centralization risk for trusted owners:

Having a single EOA as the only owner of contracts is a large centralization risk and a single point of failure. A single private key may be taken in a hack, or the sole holder of the key may become unable to retrieve the key when necessary. Consider changing to a multi-signature setup, or having a role-based authorization model.

214         function withdrawLink() public onlyOwner {
240         function setOracleAddress(address _oracleAddress) public onlyOwner {
247         function getOracleAddress() public view onlyOwner returns (address) {
254         function setJobId(string memory _jobId) public onlyOwner {
260         function getJobId() public view onlyOwner returns (string memory) {
267         function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {
278         ) public onlyOwner {
287             onlyOwner

M002 - Some ERC20 revert on zero value transfer:

Example: https://github.com/d-xo/weird-erc20#revert-on-zero-value-transfers.

158             token.safeTransferFrom(msg.sender, address(this), _amount);
200             token.safeTransfer(msg.sender, net_amount);
217                 link.transfer(msg.sender, link.balanceOf(address(this))),
229             token.safeTransfer(treasury, amount);

M003 - Large transfers may not work with some ERC20 tokens:

Some IERC20 implementations (e.g UNI, COMP) may fail if the valued transferred is larger than uint96. Source

158             token.safeTransferFrom(msg.sender, address(this), _amount);
200             token.safeTransfer(msg.sender, net_amount);
229             token.safeTransfer(treasury, amount);

L001 - Loss of precision due to division by large numbers:

Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.

290             return (oracleFee * 100) / LINK_DIVISIBILITY;

L002 - require() should be used instead of assert():

Prior to solidity version 0.8.0, hitting an assert consumes the remainder of the transaction's available gas rather than returning it, as require()/revert() do. assert() should be avoided even past solidity version 0.8.0 as its documentation states that 'The assert function creates an error of type Panic(uint256). ... Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix'.

94              assert(!midIsRedeemed[_memid]);
227             assert(amount > 0);

L003 - Missing checks for address(0x0) when assigning values to address state variables:

This issue arises when an address state variable is assigned a value without a preceding check to ensure it isn't address(0x0). This can lead to unexpected behavior as address(0x0) often represents an uninitialized address.

241             oracleAddress = _oracleAddress;

L004 - Setters should have initial value check:

Setters should have initial value check to prevent assigning wrong value to the variable. Assginment of wrong value can lead to unexpected behavior of the contract.

240         function setOracleAddress(address _oracleAddress) public onlyOwner {
241             oracleAddress = _oracleAddress;
242             _setChainlinkOracle(_oracleAddress);
243         }
254         function setJobId(string memory _jobId) public onlyOwner {
255             jobId = bytes32(bytes(_jobId));
256         }
267         function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {
268             oracleFee = _feeInJuels;
269         }
276         function setFeeInHundredthsOfLink(
277             uint256 _feeInHundredthsOfLink
278         ) public onlyOwner {
279             setFeeInJuels((_feeInHundredthsOfLink * LINK_DIVISIBILITY) / 100);
280         }

L005 - No limits when setting state variable amounts:

It is important to ensure state variables numbers are set to a reasonable value.

82              bridgeFee = _bfee; // 0.25% for the launch so uint256(25)
268             oracleFee = _feeInJuels;

L006 - Allowed fees/rates should be capped by smart contracts:

Fees/rates should be required to be below 100%, preferably at a much lower limit, to ensure users don't have to monitor the blockchain for changes prior to using the protocol.

267         function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {
268             oracleFee = _feeInJuels;
269         }
276         function setFeeInHundredthsOfLink(
277             uint256 _feeInHundredthsOfLink
278         ) public onlyOwner {
279             setFeeInJuels((_feeInHundredthsOfLink * LINK_DIVISIBILITY) / 100);
280         }

L007 - Double type casts create complexity within the code:

Double type casting should be avoided in Solidity contracts to prevent unintended consequences and ensure accurate data representation. Performing multiple type casts in succession can lead to unexpected truncation, rounding errors, or loss of precision, potentially compromising the contract's functionality and reliability. Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging. To ensure precise and consistent data handling, developers should use appropriate data types and avoid unnecessary or excessive type casting, promoting a more robust and dependable contract execution.

Strings.toHexString(uint256(uint160(msg.sender)), 20)

L008 - constructor/initialize function lacks parameter validation:

In Solidity, when values are being assigned in constructors to unsigned or integer variables, it's crucial to ensure the provided values adhere to the protocol's specific operational boundaries as laid out in the project specifications and documentation. If the constructors lack appropriate validation checks, there's a risk of setting state variables with values that could cause unexpected and potentially detrimental behavior within the contract's operations, violating the intended logic of the protocol. This can compromise the contract's security and impact the maintainability and reliability of the system. In order to avoid such issues, it is recommended to incorporate rigorous validation checks in constructors. These checks should align with the project's defined rules and constraints, making use of Solidity's built-in require function to enforce these conditions. If the validation checks fail, the require function will cause the transaction to revert, ensuring the integrity and adherence to the protocol's expected behavior.

67          constructor(
68              IERC20 _btoken,
69              address _oracleAddress,
70              address _linkTokenAddr,
71              address _treasury,
72              string memory _jobId,
73              uint256 _ofee,
74              uint256 _bfee
75          ) ConfirmedOwner(msg.sender) {
76              token = _btoken; // 0x779877A7B0D9E8603169DdbD7836e478b4624789 $LINK
77              treasury = _treasury; // 0x747D50C93e6821277805a2B80FE9CBF72EFCe6Cd
78              _setChainlinkToken(_linkTokenAddr); // 0x779877A7B0D9E8603169DdbD7836e478b4624789
79              _setChainlinkOracle(_oracleAddress); // 0x0FaCf846af22BCE1C7f88D1d55A038F27747eD2B
80              setJobId(_jobId); // "a8356f48569c434eaa4ac5fcb4db5cc0"
81              setFeeInHundredthsOfLink(_ofee); // sepolia is zero $LINK fee
82              bridgeFee = _bfee; // 0.25% for the launch so uint256(25)
83          }

L009 - Contracts are not using their OZ Upgradeable counterparts:

The non-upgradeable standard version of OpenZeppelin’s library is inherited/used by the contracts. It would be safer to use the upgradeable versions of the library contracts to avoid unexpected behavior.

Use the contracts from @openzeppelin/contracts-upgradeable instead of @openzeppelin/contracts where applicable. See https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/tree/master/contracts for a list of available upgradeable contracts

8       import {Strings} from "../lib/openzeppelin/contracts/utils/Strings.sol";
9       import "../lib/openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

L010 - Consider implementing two-step procedure for updating protocol addresses:

Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions. See similar findings in previous Code4rena contests for reference: https://code4rena.com/reports/2022-06-illuminate/#2-critical-changes-should-use-two-step-procedure

240         function setOracleAddress(address _oracleAddress) public onlyOwner {

L011 - Missing checks for address(0x0) in the constructor:

77              treasury = _treasury; // 0x747D50C93e6821277805a2B80FE9CBF72EFCe6Cd

L012 - Governance functions should be controlled by time locks:

Governance functions (such as upgrading contracts, setting critical parameters) should be controlled using time locks to introduce a delay between a proposal and its execution. This gives users time to exit before a potentially dangerous or malicious operation is applied.

Click to show 8 findings
254         function setJobId(string memory _jobId) public onlyOwner {

276         function setFeeInHundredthsOfLink(
277             uint256 _feeInHundredthsOfLink
278         ) public onlyOwner {

267         function setFeeInJuels(uint256 _feeInJuels) public onlyOwner {

247         function getOracleAddress() public view onlyOwner returns (address) {

260         function getJobId() public view onlyOwner returns (string memory) {

214         function withdrawLink() public onlyOwner {a

284         function getFeeInHundredthsOfLink()
285             public
286             view
287             onlyOwner
288             returns (uint256)
289         {

240         function setOracleAddress(address _oracleAddress) public onlyOwner {

L013 - Missing checks for address(0x0) when updating address state variables:

issing checks for address(0x0) when updating address state variables

77              treasury = _treasury; // 0x747D50C93e6821277805a2B80FE9CBF72EFCe6Cd
241             oracleAddress = _oracleAddress;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment