Overview
To make the Yearn website even more secure a new feature has recently been introduced, termed the Allowlist. In essence, it allows all transactions that are made through the website/SDK to be validated against an on-chain contract that keeps a record of all valid calldata that is possible for the website to produce.
This prevents the website from being susceptible to various man-in-the-middle attacks where data is provided to the website, whether it be through Zapper/0x’s API, or Yearn’s own. For example, if Yearn’s API returns a list of vaults with malicious addresses, with the intention that users approve one of them when trying to deposit, the Allowlist would block the transaction from being made.
We invite other protocols to also take advantage of their own Allowlists to enhance their security. Instructions for creating your own Allowlist are detailed further on.
How it works
Let’s take an example of validating the calldata for a transaction approving a deposit into a vault.
In the Allowlist contract there’s a list of conditions, which are structs that are validated against.
Here’s the one for approving a vault token (in JSON form):
id
: The identifier of the condition, allows the condition to be updated or removed by the protocol ownerimplementationId
: The identifier of the implementation contract. Implementation contracts hold the logic for validating the condition. Each Allowlist can have multiple implementation contracts to validate against. For example, on the Yearn Allowlist, there is an implementation for all things related to vaults, and another for the labs section of the website, with more experimental productsmethodName
: The function name that is allowedparamTypes
: The parameters of the functionrequirements
: Information about how the validation will be executed
There can be two types of requirements: target
and param
.
- If the first requirement argument is
target
then the next argument will be the function that should be called on the implementation to validate the target of the transaction. - If the first requirement argument is
param
then likewise the function to use for the validation comes next, and the final argument is the position of the parameter so it can be extracted from the calldata when validating.
Here’s the transaction, approving the Curve RocketPool Vault, that we will be validating
- target: 0x447Ddd4960d9fdBF6af9a790560d0AF76795CB08
- calldata:
0x095ea7b30000000000000000000000005c0a86a32c129538d62c106eb8115a8b02358d570000000000000000000000000000000000c097ce7bc90715b34b9f1000000000
There are 3 steps to validate it:
- We first check the method selector. From the condition we generate what we are expecting the method selector to be for an approval transaction. Since we have the function name and parameters stored in the condition we can recreate the function and take
bytes4(keccak256(bytes(reconstructedMethodSignature)))
. We can then compare this against the first 4 bytes of the calldata, to ensure that a valid function is being called by the website. The 4 byte signature ofapprove(address,uint256)
is0x095ea7b3
so we can see that the calldata is valid for this. - We then validate the target. To do this we make a call to the implementation contract of the condition, using the provided validation, in this case
isVaultUnderlyingToken
. We always know that we are validating an address so we can assume that that function has a single address parameter. It is also assumed that this function returns abool
. If the value returned is false then the transaction is not valid. In the implementation contract there is a functionisVaultUnderlyingToken
which then proceeds to call Yearn’s vaults registry to perform the actual validation. - We then validate all the parameter conditions, of which there can be more than one, or none in the case of a function with no arguments. In this case we want to check that the parameter in position 0 satisfies the function
isVault
on the implementation contract, this way we will know that the user is depositing into a valid vault. Again, the implementation contract uses the Yearn vault registry to check whether the address decoded from the calldata is a valid vault or not.
A transaction submitted to the Allowlist is validated against each condition, if any of them confirm that the transaction is valid then we can be confident that the transaction that is about to be submitted, however it was given to the user when interacting with the website, is not malicious and safe to execute.
Who controls each website’s Allowlist?
The Allowlist was designed so that each website would have an instance of its own, but we need some way on-chain to link each Allowlist to each website. To do this we use ENS/DNSSEC to verify the owner of each domain — https://docs.ens.domains/dns-registrar-guide. This way we know that control of the Allowlist is linked to control of the domain, and as long as this isn’t compromised the correct Allowlist for a given website can be fetched.
The security of an Allowlist also depends on the implementation contracts. If these were easily mutable, or were implemented incorrectly, then the security of the Allowlist would be compromised. It’s best to make these contracts immutable, or if they need to be updatable, then ownership by the protocol’s multisig would be preferable.
Registering as a protocol
For protocols to create and register their own Allowlist they can do the following steps:
- Start the registration of the Allowlist using
registerProtocol
on the Allowlist Registry contract. This will deploy a new Allowlist for the protocol’s domain. Note: the account starting the registration will need to be registered as the owner of the domain through ENS. - Deploy custom implementation contracts, that can be used to validate targets/parameters against
- Link these implementation contracts to the Allowlist by using the
setImplementation
function. - Figure out all transactions that are created through the website, and create corresponding conditions. Set these conditions on the Allowlist using
addConditions
An example deploy script can be found here
Future improvements
As detailed, this feature reduces the manipulation of data from which transactions users are submitting are created, such as data from APIs. However, if the source code of the website is compromised, or malicious code is injected, then these checks could simply be ignored and malicious transactions formed to present to the user.
To avoid this the Allowlist could be integrated into a wallet (such as metamask), which itself would validate all transactions that are about to be submitted against the Allowlist fetched for the domain name the transaction originated from. This would completely remove the manipulation of front-end source code from being an attack vector and would be a competitive advantage for any wallet implementing it. The Allowlist Registry can be used to validate any calldata for any website with the following function:
Further reading
Eth-Allowlist
Yearn’s allowlist implementation example
ENS DNS registration
This article was provided anonymously by a Yearn dev.
I just copy pasta — xoxo weaver.