In this article, we review Lossless Vault Protection smart contracts, discuss how to use the system directly, and comment on some gas saving implementation details of our system.
Lossless Vault Protection is a smart contracts system that allows setting up specific protection rules for an address. Vault Protection can only be used for tokens that have integrated with lossless protection smart contracts, see also https://lossless-cash.gitbook.io/lossless/technical-reference/lerc20. It can be used by token admins and allows setting up token scoped rules for token transfers execution. At the moment there are two types of rules that can be used:
- Single Limit
Before any of these rules can be turned on for a particular address in a particular token’s scope, the address and the token have to be verified by the lossless team. This permission is required to stop projects from abusing Vault Protection functionality and also to perform a gradual rollout of this feature to all of the lossless protected tokens.
Once a token is verified, its project team has to set a protection admin. The sole responsibility of the Protection admin is to control protection rules. The Protection admin can only be set by the token admin. You can read more about the token admin role on lossless protected tokens here:
The protection admin for a token can be set by using setProtectionAdmin function on LosslessGuardian smart contract. All our smart contracts can be found at https://github.com/Lossless-Cash/lossless-v2
function setProtectionAdmin(address token, address admin) external onlyVerifiedToken(token)
The address of LosslessGuardian smart contract and all the other contracts mentioned in this article can be found here:
The diagram below summarises steps that need to be executed for Vault Protection to be enabled.
Address that has a whitelist will be able to transfer tokens only to an address that’s in that whitelist. The whitelist can be set using the setProtectedAddress function on the TreasuryProtectionStrategy contract.
function setProtectedAddress(address token, address protectedAddress, address calldata whitelist) external onlyProtectionAdmin(token)
To call this function one needs to specify:
- In which token’s scope this whitelist is going to be set
- Which address should have this whitelist applied to it (the protected address)
- Whitelisted addresses (wallets that will be able to receive tokens from the protected address)
You can also see that this function can only be called by the protection admin.
To remove some addresses from a whitelist or add some new ones, the setWhitelistState function on the TreasuryProtectionStrategy smart contract should be used.
function setWhitelistState(address token, address protectedAddress, address calldata addresses, bool state) public onlyProtectionAdmin(token)
To call this function you need to specify:
- In which token’s scope you are going to set this whitelist
- Which address should have its whitelist updated
- A list of addresses that need to be removed or added from or to the whitelist
- A flag that specifies if a provided list of addresses has to be removed or added to the whitelist. Setting the flag to true will add the provided list of addresses to the whitelist and setting the flag to false would remove it from the whitelist
In order to remove the whitelist protection rule from a protected address completely, call the removeProtectedAddresses function on the LosslessGuardian smart contract with these arguments:
- In which token’s scope this whitelist was set
- The address which should have its whitelist removed
function removeProtectedAddresses(address token, address protectedAddress) external
Setting up single limit protection for an address means that this particular address will be able to transfer not more than a given number of tokens in a set period of time. For example, an address has a limit of 100 000 tokens per hour. Token transfer transactions will start failing if that address transfers out more than 100 000 tokens in an hour. However, the limit gets reset every period. Therefore, even if transactions start failing or the amount transferred is close to the limit, this only lasts until a new period is entered. In the previous example, this would mean that once one hour passes, the protected address can once again transfer up to 100 000 tokens.
Setting a limit
Setting a limit for an address can be done in one of two ways:
- Using setLimit function
- Using setLimitBatched function
Essentially, setLimitBatched works just like setLimit , except that it allows setting up the same limit for a bunch of addresses in a single transaction.
address calldata protectedAddresses,
) external onlyProtectionAdmin(token)
) external onlyProtectionAdmin(token)
Arguments for setLimit are as follows:
- In which token’s scope this limit should be set
- For which address this limit should be applied to
- The limit period in seconds
- Limit amount
- Limit start time
Please note that setting a limit in the future is not recommended as it will result in a limit that does not get reset until the start time is reached. In other words, the protection will not apply for a period of time. Therefore, the start time should either be set to the current timestamp or a timestamp in the past. Choosing a timestamp in the past allows the user to have better control over when the limit gets reset. For example, if the user wants to have a limit that resets every 24 hours, they might also want it to happen exactly at midnight (00:00). So when setting this limit the user could choose the start time to be a timestamp of the previous midnight and this would result in a limit that gets reset every 24 hours exactly at midnight.
The single limit protection strategy has a feature that allows pausing all transfers from a protected address. This is a common feature that smart contracts implement in order to mitigate incidents. However, some smart contracts just don’t have it and cannot be upgraded to include such a function. Also, wallets (Externally Owned Accounts, EOAs) do not have any way of pausing transfers. Lossless Vault Protection fixes this and allows pausing transfers from any protected address. Essentially, the transfer pausing feature uses a single limit protection strategy with a limit that’s equal to zero.
Pausing can be done by calling the pause function on the LiquidityProtectionSingleLimitStrategy smart contract. This function takes two arguments:
- Token for which these rules should be scoped
- Address for which transfers should be paused
function pause(address token, address protectedAddress) external onlyProtectionAdmin(token)
Once the transfers need to be unpaused, all you need to do is set a new limit with a valid limit amount or remove the limit protection rule completely.
Removing limits from a single address or from a bunch of addresses all at once can be done by calling the removeLimits function on the LiquidityProtectionSingleLimitStrategy smart contract. For it to work you need to provide the following two arguments:
- Token for which limit was scoped
- A list of addresses that should have their limit removed from them
function removeLimits(address token, address calldata protectedAddresses) external onlyProtectionAdmin(token)
Trusted Setup Architecture
Because some of the smart contracts are setting state in some other smart contracts, a trusted setup between different smart contracts is required. This trusted setup is created by the lossless team by calling two different functions:
The setGuardian function saves the trusted guardian smart contract address in the lossless controller smart contract. The verifyStrategies function saves a list of protection strategies (smart contracts addresses) that can be trusted by the guardian smart contract.
This pattern enables keeping only a minimal part of the whole Vault Protection state on the lossless controller. The guardian smart contract acts as a proxy and gatekeeper between the strategies and the controller. After the protection rules are saved on the controller, guardian, and protection strategy contracts, the controller is able to execute direct calls to the protection strategies and thus reduce gas costs by not proxying calls through the guardian contract. In case a picture is worth type(uint256).max words, please see the diagram below.
We are constantly striving to improve our documentation, and we strongly appreciate feedback, both negative and positive — do reach out if you see concrete ways how this writeup can be improved.
Lossless is the world’s first DeFi hack mitigation tool for token creators. Apart from our known cyber security solutions and renowned professionals, the community also plays a role. With a tangible reward system, community members are also encouraged to explore new ways to detect hacks and fraudulent transactions.
Our protocol halts counterfeit transactions through various methods of fraud identification and automatically reverses any stolen tokens back to the original owner. Our solutions to the impending problems of cyber theft within the blockchain space are thorough and applicable within many protocols.