Skip to content

Commit

Permalink
Merge branch 'well-upgradeable-impl' into multi-flow-pump-v1.1-remedi…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
pizzaman1337 committed Jun 21, 2024
2 parents 914a0ac + dd66d36 commit 95b7f19
Show file tree
Hide file tree
Showing 9 changed files with 537 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
cache/
out/

.vscode

# Ignores development broadcast logs
/broadcast

Expand Down
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ out = 'out'
libs = ['lib', 'node_modules']
fuzz = { runs = 256 }
optimizer = true
optimizer_runs = 800
optimizer_runs = 400
remappings = [
'@openzeppelin/=node_modules/@openzeppelin/',
]
Expand Down
14 changes: 14 additions & 0 deletions mocks/wells/MockWellUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {WellUpgradeable} from "src/WellUpgradeable.sol";

// this needs to be here for upgrade checks
/// @custom:oz-upgrades-from WellUpgradeable
contract MockWellUpgradeable is WellUpgradeable {

function getVersion(uint256 i) external pure returns (uint256) {
return i;
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@beanstalk/wells",
"version": "1.1.0-prerelease0",
"version": "1.2.0-prerelease0",
"description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.",
"main": "index.js",
"directories": {
Expand Down
25 changes: 25 additions & 0 deletions script/helpers/WellDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {LibContractInfo} from "src/libraries/LibContractInfo.sol";
import {LibWellConstructor} from "src/libraries/LibWellConstructor.sol";
import {Well, Call, IERC20} from "src/Well.sol";
import {Aquifer} from "src/Aquifer.sol";
import {WellUpgradeable} from "src/WellUpgradeable.sol";
import {LibWellUpgradeableConstructor} from "src/libraries/LibWellUpgradeableConstructor.sol";

abstract contract WellDeployer {
/**
Expand All @@ -28,4 +30,27 @@ abstract contract WellDeployer {
LibWellConstructor.encodeWellDeploymentData(_aquifer, _tokens, _wellFunction, _pumps);
_well = Well(Aquifer(_aquifer).boreWell(_wellImplementation, immutableData, initData, _salt));
}


/**
* @notice Encode the Well's immutable data, and deploys the well. Modified for upgradeable wells.
* @param _aquifer The address of the Aquifer which will deploy this Well.
* @param _wellImplementation The address of the Well implementation.
* @param _tokens A list of ERC20 tokens supported by the Well.
* @param _wellFunction A single Call struct representing a call to the Well Function.
* @param _pumps An array of Call structs representings calls to Pumps.
* @param _salt The salt to deploy the Well with (`bytes32(0)` for none). See {LibClone}.
*/
function encodeAndBoreWellUpgradeable(
address _aquifer,
address _wellImplementation,
IERC20[] memory _tokens,
Call memory _wellFunction,
Call[] memory _pumps,
bytes32 _salt
) internal returns (WellUpgradeable _well) {
(bytes memory immutableData, bytes memory initData) =
LibWellUpgradeableConstructor.encodeWellDeploymentData(_aquifer, _tokens, _wellFunction, _pumps);
_well = WellUpgradeable(Aquifer(_aquifer).boreWell(_wellImplementation, immutableData, initData, _salt));
}
}
2 changes: 1 addition & 1 deletion src/Well.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr
_disableInitializers();
}

function init(string memory _name, string memory _symbol) external initializer {
function init(string memory _name, string memory _symbol) external virtual initializer {
__ERC20Permit_init(_name);
__ERC20_init(_name, _symbol);
__ReentrancyGuard_init();
Expand Down
163 changes: 163 additions & 0 deletions src/WellUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Well} from "src/Well.sol";
import {UUPSUpgradeable} from "ozu/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "ozu/access/OwnableUpgradeable.sol";
import {IERC20, SafeERC20} from "oz/token/ERC20/utils/SafeERC20.sol";
import {IAquifer} from "src/interfaces/IAquifer.sol";
import {console} from "forge-std/console.sol";

/**
* @title WellUpgradeable
* @author Publius, Silo Chad, Brean, Deadmanwalking
* @dev A Well is a constant function AMM allowing the provisioning of liquidity
* into a single pooled on-chain liquidity position.
*
* Given the dynamic storage layout of Wells initialized by an minimal proxy,
* Creating an upgradeable Well requires a custom initializer function that allows the Well
* to be initialized with immutable storage, but does not deploy a Well token.
*
* Rebasing Tokens:
* - Positive rebasing tokens are supported by Wells, but any tokens recieved from a
* rebase will not be rewarded to LP holders and instead can be extracted by anyone
* using `skim`, `sync` or `shift`.
* - Negative rebasing tokens should not be used in Well as the effect of a negative
* rebase will be realized by users interacting with the Well, not LP token holders.
*
* Fee on Tranfer (FoT) Tokens:
* - When transferring fee on transfer tokens to a Well (swapping from or adding liquidity),
* use `swapFromFeeOnTrasfer` or `addLiquidityFeeOnTransfer`. `swapTo` does not support
* fee on transfer tokens (See {swapTo}).
* - When recieving fee on transfer tokens from a Well (swapping to and removing liquidity),
* INCLUDE the fee that is taken on transfer when calculating amount out values.
*/
contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable {
address private immutable ___self = address(this);

/**
* @notice verifies that the execution is called through an minimal proxy or is not a delegate call.
*/
modifier notDelegatedOrIsMinimalProxy() {
if (address(this) != ___self) {
address aquifer = aquifer();
address wellImplmentation = IAquifer(aquifer).wellImplementation(address(this));
require(wellImplmentation == ___self, "Function must be called by a Well bored by an aquifer");
} else {
revert("UUPSUpgradeable: must not be called through delegatecall");
}
_;
}

function init(
string memory _name,
string memory _symbol
) external override reinitializer(2) {
// owner of Well param as the aquifier address will be the owner initially
// ownable init transfers ownership to msg.sender
__ERC20Permit_init(_name);
__ERC20_init(_name, _symbol);
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
__Ownable_init();

IERC20[] memory _tokens = tokens();
uint256 tokensLength = _tokens.length;
for (uint256 i; i < tokensLength - 1; ++i) {
for (uint256 j = i + 1; j < tokensLength; ++j) {
if (_tokens[i] == _tokens[j]) {
revert DuplicateTokens(_tokens[i]);
}
}
}
}

/**
* @notice `initClone` allows for the Well to be initialized without deploying a Well token.
* @dev This function is required given intializing with the Well token would create two valid Wells.
* Sets ReentraryGuard to true to prevent users from interacting with the Well.
*/
function initNoWellToken() external initializer {}

// Wells deployed by aquifers use the EIP-1167 minimal proxy pattern for gas-efficent deployments.
// This pattern breaks the UUPS upgrade pattern, as the `__self` variable is set to the initial well implmentation.
// `_authorizeUpgrade` and `upgradeTo` are modified to allow for upgrades to the Well implementation.
// verification is done by verifying the ERC1967 implmentation (the well address) maps to the aquifers well -> implmentation mapping.

/**
* @notice Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an ERC1167 minimal proxy from an aquifier, pointing to a well implmentation.
*/
function _authorizeUpgrade(address newImplmentation) internal view override {
// verify the function is called through a delegatecall.
require(address(this) != ___self, "Function must be called through delegatecall");

// verify the function is called through an active proxy bored by an aquifer.
address aquifer = aquifer();
address activeProxy = IAquifer(aquifer).wellImplementation(_getImplementation());
require(activeProxy == ___self, "Function must be called through active proxy bored by an aquifer");

// verify the new implmentation is a well bored by an aquifier.
require(
IAquifer(aquifer).wellImplementation(newImplmentation) !=
address(0),
"New implementation must be a well implmentation"
);

// verify the new implmentation is a valid ERC-1967 implmentation.
console.log("here");
require(
UUPSUpgradeable(newImplmentation).proxiableUUID() ==
_IMPLEMENTATION_SLOT,
"New implementation must be a valid ERC-1967 implmentation"
);
}

/**
* @notice Upgrades the implementation of the proxy to `newImplementation`.
* Calls {_authorizeUpgrade}.
* @dev `upgradeTo` was modified to support ERC-1167 minimal proxies
* cloned (Bored) by an Aquifer.
*/
function upgradeTo(address newImplementation) public override {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
}

/**
* @notice Upgrades the implementation of the proxy to `newImplementation`.
* Calls {_authorizeUpgrade}.
* @dev `upgradeTo` was modified to support ERC-1167 minimal proxies
* cloned (Bored) by an Aquifer.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable override {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data, true);
}

/**
* @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. However, Wells bored by Aquifers
* are ERC-1167 minimal immutable clones and cannot delgate to another proxy. Thus, `proxiableUUID` was updated to support
* this specific usecase.
*/
function proxiableUUID() external view override notDelegatedOrIsMinimalProxy returns (bytes32) {
return _IMPLEMENTATION_SLOT;
}

function getImplementation() external view returns (address) {
return _getImplementation();
}

function getVersion() external pure virtual returns (uint256) {
return 1;
}

function getInitializerVersion() external view returns (uint256) {
return _getInitializedVersion();
}
}
87 changes: 87 additions & 0 deletions src/libraries/LibWellUpgradeableConstructor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
// forgefmt: disable-start

pragma solidity ^0.8.20;

import {LibContractInfo} from "src/libraries/LibContractInfo.sol";
import {Call, IERC20} from "src/Well.sol";
import {WellUpgradeable} from "src/WellUpgradeable.sol";

library LibWellUpgradeableConstructor {

/**
* @notice Encode the Well's immutable data.
*/
function encodeWellDeploymentData(
address _aquifer,
IERC20[] memory _tokens,
Call memory _wellFunction,
Call[] memory _pumps
) internal pure returns (bytes memory immutableData, bytes memory initData) {
immutableData = encodeWellImmutableData(_aquifer, _tokens, _wellFunction, _pumps);
initData = abi.encodeWithSelector(WellUpgradeable.initNoWellToken.selector);
}

/**
* @notice Encode the Well's immutable data.
* @param _aquifer The address of the Aquifer which will deploy this Well.
* @param _tokens A list of ERC20 tokens supported by the Well.
* @param _wellFunction A single Call struct representing a call to the Well Function.
* @param _pumps An array of Call structs representings calls to Pumps.
* @dev `immutableData` is tightly packed, however since `_tokens` itself is
* an array, each address in the array will be padded up to 32 bytes.
*
* Arbitrary-length bytes are applied to the end of the encoded bytes array
* for easy reading of statically-sized data.
*
*/
function encodeWellImmutableData(
address _aquifer,
IERC20[] memory _tokens,
Call memory _wellFunction,
Call[] memory _pumps
) internal pure returns (bytes memory immutableData) {

immutableData = abi.encodePacked(
_aquifer, // aquifer address
_tokens.length, // number of tokens
_wellFunction.target, // well function address
_wellFunction.data.length, // well function data length
_pumps.length, // number of pumps
_tokens, // tokens array
_wellFunction.data // well function data (bytes)
);
for (uint256 i; i < _pumps.length; ++i) {
immutableData = abi.encodePacked(
immutableData, // previously packed pumps
_pumps[i].target, // pump address
_pumps[i].data.length, // pump data length
_pumps[i].data // pump data (bytes)
);
}
}

function encodeWellInitFunctionCall(
IERC20[] memory _tokens,
Call memory _wellFunction
) public view returns (bytes memory initFunctionCall) {
string memory name = LibContractInfo.getSymbol(address(_tokens[0]));
string memory symbol = name;
for (uint256 i = 1; i < _tokens.length; ++i) {
name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i])));
symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i])));
}
name = string.concat(name, " ", LibContractInfo.getName(_wellFunction.target), " Upgradeable Well");
symbol = string.concat(symbol, LibContractInfo.getSymbol(_wellFunction.target), "uw");

// See {Well.init}.
initFunctionCall = abi.encodeWithSelector(WellUpgradeable.init.selector, name, symbol);
}

/**
* @notice Encode a Call struct representing an arbitrary call to `target` with additional data `data`.
*/
function encodeCall(address target, bytes memory data) public pure returns (Call memory) {
return Call(target, data);
}
}
Loading

0 comments on commit 95b7f19

Please sign in to comment.