# SIP 64: Flexible Contract Storage Source

Author Justin J Moses Proposed 2020-05-28

## Simple Summary

Provide a reusable storage location for any Synthetix contract.

## Abstract

Take the EternalStorage contract pattern and generalize it for use in any number of various contracts.

## Motivation

Currently the EternalStorage contract pattern is useful as storage for a single contract. However, using it means every new section of Synthetix requires its own instance of the storage contract, which continues to expand surface area of the project and requires more maintainence as each is paired to one single contract. Instead, this SIP proposes to create one central storage contract, where any contract can access storage mapped to

## Specification

### Overview

This SIP proposes to create a new version of the EternalStorage contract that instead of being limited to a single associatedContract via the State mixin, it can support any number of contracts that wish to use it in a safe and segregated manner.

All getters and setters from EternalStorage will be used, yet with an extra initial parameter, contractName. This property will be the mapping for the storage entries. For any setter function, the msg.sender must match the address that the Synthetix AddressResolver has for it. As such, this storage contract must manage a reference to the AddressResolver (and this can be done one-time using the ReadProxyAddressResolver).

## Resolved Questions

1. Should the getters be limited to only be read the contract as well? This would prevent other internal Synthetix contracts from reading directly from the storage contract on behalf of another contract and would prevent third party contracts from reading these values on-chain. The cost is that this limitation may have unforeseen consequences for integrations down the line, moreover it is slightly more gas efficint to look up the storage directly, whereas the benefit is that the contracts themselves are the abstraction for viewing storage, and it could be problematic to allow third party contracts to expect the storage to be formatted a certain way, and break those expectations in future releases.
• Decision: No. This would add unnecessary friction to the process.
1. How to handle future refactoring of contracts? If we were to store data for Issuer say, and then we split out burning from Issuer into Burner, how could we reuse storage from Issuer in Burner?

1. Use contract-auth calls to migrate the data over to the new mapping, one key at a time. This is manageable in the case of settings and properties, but much more onerous for address-based keys such as how Issuer uses EternalStorage.

2. A potentially better solution (though it could get ugly) is having an available contract mapping. Initially the mapping is empty but it could be added to by a contract that allows other named contracts in the AddressResolver to set/get on its behalf. So for the above example, Issuer would have something added to itself that says Storage.addContractMapping("Issuer", "Burner") that would allow Burner to pass through Issuer as a key and still have write access to that space. This isn’t great because then Burner needs to keep a reference to the "Issuer" storage key, but is manageable. Any other suggestions?

• Decision: A version of #2 based off the proposal by @zyzek. The key to the entry can be stored in a mapping and if a migration is allowed then an additional mapping entry is created for the new contract to the old one.

### Rationale

The current EternalStorage contract is quite flexible in how it stores state, it makes sense to double down on this useful pattern, hence a new version based off of it (but not extending it as we don’t want to use the State mixin here). One trade-off of this approach however is the storage of packed structs. If a struct condenses it’s entries down to maximize space: e.g. SystemStatus.Suspension (which uses a bool that is uint8 in Solidity, combined with a uint248 reason code which makes up the remainder of the 32 bytes in the storage slot), then it would be less efficient to store the individual components into two separate storage slots.

Unfortunately, there’s no easy solution to generalizing the storage of structs. The usage of this storage contract will come down to whether or not the contract in question can efficiently compress and decompress its required data before going in and out of the storage contract.

Additionally, there will be a slightly higher gas cost when persisting storage now as each contract will need to do a cross-contract call both a) to the new Storage contract and then b) from the new Storage contract to the AddressResolver to ascertain if msg.sender is indeed the address it expects from the AddressResolver. This second step cannot be alleviated by having Storage use MixinResolver as other Synthetix contracts do because Storage is not an upgradable contract, and thus we can’t hard-code the names of the contracts it needs to store in its cache.

### Technical Specification

The API is nearly identical to EternalStorage with a few exceptions:

1. All getters and setters take an additional first parameter, the contractName as bytes32
2. All getters and setters will additionally take a memory array of records and values (for setters) to reduce external calls where possible
3. Basic migration functionality to move keys over to a new contractName. This addresses question 2a above. 2b would require more functionality.
interface IContractStorage {

function getUintValue(bytes32 contractName, bytes32 record) external view returns (uint);

function getUintValues(bytes32 contractName, bytes32[] calldata records) external view returns (uint[] memory);

// only contract
function setUIntValue(bytes32 contractName, bytes32 record, uint value) external;

// only contract
function setUIntValues(bytes32 contractName, bytes32[] calldata records, uint[] values) external;

// (as above for bytes32, bool, string and address values)
// ...

// only contract
function migrateKey(bytes32 contractName, bytes32 record) external;

// only contract
function migrateKeys(bytes32 contractName, bytes32[] calldata records) external;
}


Additionally, contracts now need to know their own contractName. To solve this, we can add another constructor argument to MixinResolver with the contract’s name added to itself as a public property, which it can then use for getting and setting storage. Though this may be superceded if a refactor occurs - see Question 2 above.

#### Proposed Usages

• The list of synths managed by Issuer (previously in Synthetix until SIP-48).
• IssuanceEternalStorage can be replaced by wholesale by this
• FeePoolEternalStorage can be replaced by this by additionally storing the data from fee periods into this as well as FeePoolEternalStorage during the transition period (two week claim window). The following upgrade can then remove this.
• All SCCP configurable settings, managed by a new contract SystemSetting. This contract will be owned specifically by the protocolDAO in order to expedite any SCCP change without requiring a migration contract (from SIP-59).
Contract Property Type Notes
BinaryOptionMarketManager creatorLimits (capitalRequirement, skewLimit) uint Proposed in SIP 53; not yet deployed.
BinaryOptionMarketManager fees (creatorFee, poolFee, refundFee) uint (ibid)
BinaryOptionMarketManager durations (maxOraclePriceAge, expiryDuration, maxTimeToMaturity) uint (ibid)
Exchanger waitingPeriodSecs uint
Exchanger exchangeFeeRateForSynths mapping(bytes32 => uint) (currently on FeePool)
ExchangeRates rateStalePeriod uint
ExchangeRates aggregators mapping(bytes32 => address)
FeePool feePeriodDuration uint
FeePool targetThreshold uint
Issuer minimumStakeTime uint
SynthetixState issuanceRatio uint Cannot be modified directly, so all references need to be updated instead

### Test Cases

Test cases for an implementation are mandatory for SIPs but can be included with the implementation.

### Configurable Values (Via SCCP)

Please list all values configurable via SCCP under this implementation.

Copyright and related rights waived via CC0.