Archblock Stablecoins & GBPT Upgrade
Smart Contract Security Assessment
Jan 24, 2024

SUMMARY
ABSTRACT
Dedaub was commissioned to perform a security audit of the Archblock Stablecoins Protocol. Some issues were found with the most important focusing on the upgrade process of PoundToken (previously GBPT) to the new 1GBP implementation and a possible DoS to specific operations of the protocol under certain circumstances.
PROTOCOL DESCRIPTION
Archblock has developed a stablecoin protocol with an integrated Proof of Reserves system. Chainlink feeds can be used to attest to the reserves held by the stablecoin issuer’s banking partners and publish this information on-chain. The protocol is only able to mint an amount of tokens which does not exceed the reserves indicated by the feed at any given time.
The protocol has the ability to create a number of pools, each of which has a certain value, which can be spent to create stablecoin tokens. The value in each pool cannot exceed a given limit, and a threshold is used to determine how many tokens can be minted at once.
Pools are arranged in a chain, and an account with the DEFAULT_ADMIN_ROLE is able to top up the value of the last pool in the chain. Following this, the value of the previous pool in the chain can be refilled up to its limit as long as the right number of accounts with the MINT_RATIFIER_ROLE have approved the refill.
Accounts with the MINTER_ROLE are allowed to create an operation asking for a certain value to be minted into stablecoin tokens. Accounts with the MINT_RATIFIER_ROLE will then be able to approve such an operation. It can then be executed against any pool which will accept to mint tokens given the current number of signatures.
Accounts which own stablecoin tokens are able to redeem them by sending them to an account with an address \<= MAX_REDEMPTION_ADDRESS which also has been assigned the REDEMPTION_ADDRESS_ROLE. Upon such transfer the tokens are immediately burned from the redemption account. Every user interacting with Archblock Stablecoins will be assigned a redemption account which can be used to account for the redemption of the stablecoin tokens into fiat currency.
The protocol has the ability to freeze transfers of the stablecoin to and from certain addresses, by assigning them the FROZEN_ROLE, and can also be paused and unpaused by accounts which have the PAUSER_ROLE.
SETTING & CAVEATS
This audit report mainly covers the code of the at-the-time private trusttoken/contracts-stablecoins repository, found at commit e2c1640062339015616128753e7cd52234c7732a.
We were also requested to review the upgrade process from the old PoundToken, found at src_0_8_4_opt_200_gbpt/BFTokenUpgradable, to the new 1GBP implementation (GBPT_StablecoinWithProxyStorage contract). The storage layout and deployment scripts under the script/ folder were also examined. Some considerations were raised during the auditing of that process which are described in the UPGRADE-PROCESS CONSIDERATIONS section.
As part of the audit, we also reviewed the fixes for the issues included in the report. The fixes were inspected at commit 696a7513861baefeb4dfe37d823a034d1df201a8 and we found that they have been implemented correctly.
Two auditors worked on the codebase for 8 days on the following contracts:
The audit’s main target is security threats, i.e., what the community understanding would likely call "hacking", rather than the regular use of the protocol. Functional correctness (i.e. issues in "regular use") is a secondary consideration. Typically it can only be covered if we are provided with unambiguous (i.e. full-detail) specifications of what is the expected, correct behavior. In terms of functional correctness, we often trusted the code’s calculations and interactions, in the absence of any other specification. Functional correctness relative to low-level calculations (including units, scaling and quantities returned from external protocols) is generally most effectively done through thorough testing rather than human auditing.
PROTOCOL-LEVEL CONSIDERATIONS
It is a common approach for the contracts that are intended to be upgradeable to append a fixed-size uint256[X] array at the bottom of each contract to allow state variable expansion in future versions. However, such additions need special care as some caveats could lead to misaligned storage layouts between the old and the new implementations.
For example, variables less than 32-bytes long can be combined together in the same slot if they fit. So, if, let’s say, 8 new bool variables are added one after another, the __gap[50] should not shrink by 8 slots, but only by 1 and become __gap[49] (see also M1 and A6).
We mention this for future awareness and because several places contain such gap arrays including Structs and the ProxyStorage as well. Namely:
-
src_0_8_19_opt_20000/library/ProofOfReserve.sol:Params -
src_0_8_19_opt_20000/library/Redemption.sol:Params -
src_0_8_19_opt_20000_1gbp/library/ProofOfReserve.sol:Params -
src_0_8_19_opt_20000_1gbp/library/Redemption.sol:Params -
src_0_8_19_opt_20000_1gbp/abstract/GBPT_ProxyStorage.sol -
src_0_8_19_opt_20000_1gbp/abstract/ProxyStorage.sol
As a side note, another common convention (assuming OZ’s implementations) is that these gap arrays have an initial fixed-size of 50 words and then the number of slots acquired by the existing storage variables is subtracted. For example, ERC20Upgradeable has 5 state variables that acquire 5 slots in total, hence the uint256[45] gap at the bottom of the contract. This, however, doesn’t seem to be exactly followed in the two structs listed above which declare uint256[50] __gap arrays, but they also have several fields declared. We mention this just for completeness and consistency in case such a standard is desired to be followed throughout the codebase.