Skip to content

Commit

Permalink
Merge pull request #168 from gammaswap/feat/minimal-beacon-proxy
Browse files Browse the repository at this point in the history
publish package
  • Loading branch information
0xDanr committed Dec 22, 2023
2 parents 82a9bdd + 696831a commit df642aa
Show file tree
Hide file tree
Showing 11 changed files with 527 additions and 53 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,15 @@ To publish an npm package follow the following steps
1. If change does not break interface, then it's a patch version update
2. If change breaks interface, then it's a minor version update
3. If change is for a new product release to public, it's a major version update

### How to Generate Minimal Beacon Proxy Bytecode

The source code for the Minimal Beacon Proxy is in /contracts/utils/MinimalBeaconProxy.sol

1. Disable bytecode metadata hash in hardhat config file
solidity: { settings: { metadata: { bytecodeHash: "none" } } }
2. Run 'npx hardhat compile'
3. Retrieve bytecode from MinimalBeaconProxy.json file in artifacts

*The reason for the changes in the bytecode depending on protocolId > 256 is because if protocolId > 256 then it takes
2 bytes in the bytecode instead of 1 byte, which means the bytecode must allocate this space.
15 changes: 14 additions & 1 deletion contracts/GammaPoolFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ contract GammaPoolFactory is AbstractGammaPoolFactory, AbstractRateParamsStore,
getProtocol[IGammaPool(implementation).protocolId()] = implementation; // store implementation
}

function updateProtocol(uint16 _protocolId, address _newImplementation) external virtual override onlyOwner {
isProtocolNotSet(_protocolId);
if(IGammaPool(_newImplementation).protocolId() == 0) revert ZeroProtocol();
if(IGammaPool(_newImplementation).protocolId() != _protocolId) revert ProtocolMismatch();
if(getProtocol[_protocolId] == _newImplementation) revert ProtocolExists(); // protocolId already set with same implementation

getProtocol[_protocolId] = _newImplementation;
}

/// @dev See {IGammaPoolFactory-removeProtocol}
function removeProtocol(uint16 _protocolId) external virtual override onlyOwner {
getProtocol[_protocolId] = address(0);
Expand Down Expand Up @@ -89,7 +98,11 @@ contract GammaPoolFactory is AbstractGammaPoolFactory, AbstractRateParamsStore,
hasPool(key); // check this instance hasn't already been created

// instantiate GammaPool proxy contract address for protocol's implementation contract using unique key as salt for the pool's address
pool = cloneDeterministic(implementation, key);
if (_protocolId < 10000) {
pool = cloneDeterministic(_protocolId, key);
} else {
pool = cloneDeterministic2(implementation, key);
}

uint8[] memory _decimals = getDecimals(_tokensOrdered);
uint72 _minBorrow = uint72(10**((_decimals[0] + _decimals[1]) / 2));
Expand Down
13 changes: 12 additions & 1 deletion contracts/base/AbstractGammaPoolFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "../interfaces/IGammaPoolFactory.sol";
import "../interfaces/IGammaPool.sol";
import "../interfaces/IPausable.sol";
import "../utils/TwoStepOwnable.sol";
import "../libraries/AddressCalculator.sol";

/// @title Abstract factory contract to create more GammaPool contracts.
/// @author Daniel D. Alcarraz (https://github.com/0xDanr)
Expand All @@ -15,6 +16,7 @@ abstract contract AbstractGammaPoolFactory is IGammaPoolFactory, TwoStepOwnable
error ZeroProtocol();
error ProtocolNotSet();
error ProtocolExists();
error ProtocolMismatch();
error ProtocolRestricted();
error PoolExists();
error DeployFailed();
Expand Down Expand Up @@ -106,6 +108,15 @@ abstract contract AbstractGammaPoolFactory is IGammaPoolFactory, TwoStepOwnable
feeToSetter = _feeToSetter;
}

function cloneDeterministic(uint16 protocolId, bytes32 salt) internal virtual returns (address instance) {
bytes memory bytecode = AddressCalculator.calcMinimalBeaconProxyBytecode(protocolId, salt, address(this));

assembly {
instance := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
if(instance == address(0)) revert DeployFailed();
}

/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
Expand All @@ -117,7 +128,7 @@ abstract contract AbstractGammaPoolFactory is IGammaPoolFactory, TwoStepOwnable
* @param salt - the bytes32 key that is unique to the GammaPool and therefore also used as a unique identifier of the GammaPool
* @return instance - address of GammaPool that was created
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
function cloneDeterministic2(address implementation, bytes32 salt) internal virtual returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/IGammaPoolFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ interface IGammaPoolFactory {
/// @param _implementation - implementation address of GammaPool proxy contract. Because all GammaPools are created as proxy contracts
function addProtocol(address _implementation) external;

/// @notice Only owner of GammaPoolFactory can call this function
/// @dev Update protocol implementation for a protocol.
/// @param _protocolId - id identifier of GammaPool implementation
/// @param _newImplementation - implementation address of GammaPool proxy contract. Because all GammaPools are created as proxy contracts
function updateProtocol(uint16 _protocolId, address _newImplementation) external;

/// @notice Only owner of GammaPoolFactory can call this function
/// @dev Removing protocol implementation from GammaPoolFactory contract. Which means GammaPoolFactory will no longer be able to create GammaPools with this implementation (protocol)
/// @param _protocolId - id identifier of GammaPool implementation
Expand Down
56 changes: 53 additions & 3 deletions contracts/libraries/AddressCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ library AddressCalculator {
return keccak256(abi.encode(cfmm, protocolId)); // key is hash of CFMM address and protocolId
}

/// @dev calculate deterministic address to instantiate GammaPool proxy contract
/// @dev calculate deterministic address to instantiate GammaPool minimal beacon proxy or minimal proxy contract
/// @param factory - address of factory that will instantiate GammaPool proxy contract
/// @param protocolId - protocol id of instance address the GammaPool will use (version of this GammaPool)
/// @param key - salt used in address generation to assure its uniqueness
/// @return _address - address of GammaPool that maps to protocolId and key
function calcAddress(address factory, uint16 protocolId, bytes32 key) internal view returns (address) {
return predictDeterministicAddress(IGammaPoolFactory(factory).getProtocol(protocolId), key, factory);
if (protocolId < 10000) {
return predictDeterministicAddress(protocolId, key, factory);
} else {
return predictDeterministicAddress2(IGammaPoolFactory(factory).getProtocol(protocolId), key, factory);
}
}

/// @dev calculate a deterministic address based on init code hash
Expand All @@ -34,12 +38,58 @@ library AddressCalculator {
return address(uint160(uint256(keccak256(abi.encodePacked(hex"ff",factory,salt,initCodeHash)))));
}

/// @dev Compute bytecode of a minimal beacon proxy contract, excluding bytecode metadata hash
/// @param protocolId - id of protocol
/// @param salt - salt used in address generation to assure its uniqueness
/// @param factory - address of factory that instantiated or will instantiate this contract
/// @return bytecode - the calculated bytecode for minimal beacon proxy contract
function calcMinimalBeaconProxyBytecode(
uint16 protocolId,
bytes32 salt,
address factory
) internal pure returns(bytes memory) {
return abi.encodePacked(
hex"6080604052348015600f57600080fd5b5060",
protocolId < 256 ? hex"6c" : hex"6d",
hex"8061001e6000396000f3fe608060408190526334b1f0a960e21b8152",
protocolId < 256 ? hex"60" : hex"61",
protocolId < 256 ? abi.encodePacked(uint8(protocolId)) : abi.encodePacked(protocolId),
hex"60845260208160248173",
factory,
hex"5afa60",
protocolId < 256 ? hex"3a" : hex"3b",
hex"573d6000fd5b5060805160003681823780813683855af491503d81823e81801560",
protocolId < 256 ? hex"5b" : hex"5c",
hex"573d82f35b3d82fdfea164736f6c6343000815000a"
);
}

/// @dev Computes the address of a minimal beacon proxy contract
/// @param protocolId - id of protocol
/// @param salt - salt used in address generation to assure its uniqueness
/// @param factory - address of factory that instantiated or will instantiate this contract
/// @return predicted - the calculated address
function predictDeterministicAddress(
uint16 protocolId,
bytes32 salt,
address factory
) internal pure returns (address) {
bytes memory bytecode = calcMinimalBeaconProxyBytecode(protocolId, salt, factory);

// Compute the hash of the initialization code.
bytes32 bytecodeHash = keccak256(bytecode);

// Compute the final CREATE2 address
bytes32 data = keccak256(abi.encodePacked(bytes1(0xff), factory, salt, bytecodeHash));
return address(uint160(uint256(data)));
}

/// @dev Computes the address of a minimal proxy contract
/// @param implementation - address of implementation contract of this minimal proxy contract
/// @param salt - salt used in address generation to assure its uniqueness
/// @param factory - address of factory that instantiated or will instantiate this contract
/// @return predicted - the calculated address
function predictDeterministicAddress(
function predictDeterministicAddress2(
address implementation,
bytes32 salt,
address factory
Expand Down
5 changes: 4 additions & 1 deletion contracts/test/TestGammaPoolFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract TestGammaPoolFactory is AbstractGammaPoolFactory {
function createPool2(bytes calldata _data) external virtual returns(address pool) {
bytes32 key = AddressCalculator.getGammaPoolKey(cfmm, protocolId);

pool = cloneDeterministic(protocol, key);
pool = cloneDeterministic(protocolId, key);
decimals[0] = 18;
decimals[1] = 18;
IGammaPool(pool).initialize(cfmm, tokens, decimals, uint72(1e3), _data);
Expand All @@ -47,6 +47,9 @@ contract TestGammaPoolFactory is AbstractGammaPoolFactory {
protocol = _protocol;
}

function updateProtocol(uint16 _protocolId, address _newImpl) external override {
}

function removeProtocol(uint16) external override {
protocol = address(0);
}
Expand Down
3 changes: 3 additions & 0 deletions contracts/test/TestGammaPoolFactory2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ contract TestGammaPoolFactory2 is AbstractGammaPoolFactory {
protocol = _protocol;
}

function updateProtocol(uint16 _protocolId, address _newImpl) external override {
}

function removeProtocol(uint16) external override {
protocol = address(0);
}
Expand Down
35 changes: 35 additions & 0 deletions contracts/utils/MinimalBeaconProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

contract MinimalBeaconProxy {
fallback() external payable virtual {
assembly {
let p := mload(0x40)
// Call GammaPoolFactory -> getProtocol(uint16)
mstore(p, 0xd2c7c2a400000000000000000000000000000000000000000000000000000000)
mstore(add(p, 4), 0xffff)
let result := staticcall(gas(), 0xBEbeBeBEbeBebeBeBEBEbebEBeBeBebeBeBebebe, p, 0x24, 0x80, 0x20)
if iszero(result) {
revert(0, returndatasize())
}
let impl := mload(0x80)
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gammaswap/v1-core",
"version": "1.1.14",
"version": "1.1.15",
"description": "Core smart contracts for the GammaSwap V1 protocol",
"homepage": "https://gammaswap.com",
"scripts": {
Expand Down

0 comments on commit df642aa

Please sign in to comment.