Skip to content

Commit

Permalink
Merge pull request #703 from bosonprotocol/voucher-create2
Browse files Browse the repository at this point in the history
Clone vouchers using create2
  • Loading branch information
mischat authored Jul 7, 2023
2 parents d358797 + 1ec9c3c commit c6d0e08
Show file tree
Hide file tree
Showing 23 changed files with 874 additions and 285 deletions.
1 change: 1 addition & 0 deletions contracts/domain/BosonConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ string constant ESCALATION_NOT_ALLOWED = "Disputes without dispute resolver cann
// Revert Reasons: Config related
string constant FEE_PERCENTAGE_INVALID = "Percentage representation must be less than 10000";
string constant VALUE_ZERO_NOT_ALLOWED = "Value must be greater than 0";
bytes32 constant VOUCHER_PROXY_SALT = keccak256(abi.encodePacked("BosonVoucherProxy"));

// EIP712Lib
string constant PROTOCOL_NAME = "Boson Protocol";
Expand Down
12 changes: 9 additions & 3 deletions contracts/protocol/bases/SellerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents {
storeSeller(_seller, _authToken, lookups);

// Create clone and store its address cloneAddress
address voucherCloneAddress = cloneBosonVoucher(sellerId, 0, _seller.assistant, _voucherInitValues);
address voucherCloneAddress = cloneBosonVoucher(sellerId, 0, sender, _seller.assistant, _voucherInitValues, "");
lookups.cloneAddress[sellerId] = voucherCloneAddress;

// Notify watchers of state change
Expand Down Expand Up @@ -142,36 +142,42 @@ contract SellerBase is ProtocolBase, IBosonAccountEvents {

// Map the seller's other addresses to the seller id. It's not necessary to map the treasury address, as it only receives funds
_lookups.sellerIdByAssistant[_seller.assistant] = _seller.id;
_lookups.sellerCreator[_seller.id] = msgSender();
}

/**
* @notice Creates a minimal clone of the Boson Voucher Contract.
*
* @param _sellerId - id of the seller
* @param _collectionIndex - index of the collection.
* @param _creator - address of the seller creator
* @param _assistant - address of the assistant
* @param _voucherInitValues - the fully populated BosonTypes.VoucherInitValues struct
* @param _externalId - external collection id ("" for the default collection)
* @return cloneAddress - the address of newly created clone
*/
function cloneBosonVoucher(
uint256 _sellerId,
uint256 _collectionIndex,
address _creator,
address _assistant,
VoucherInitValues calldata _voucherInitValues
VoucherInitValues calldata _voucherInitValues,
string memory _externalId
) internal returns (address cloneAddress) {
// Pointer to stored addresses
ProtocolLib.ProtocolAddresses storage pa = protocolAddresses();

// Load beacon proxy contract address
bytes20 targetBytes = bytes20(pa.beaconProxy);
bytes32 salt = keccak256(abi.encodePacked(_creator, _externalId));

// create a minimal clone
assembly {
let clone := mload(0x40)
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone, 0x14), targetBytes)
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
cloneAddress := create(0, clone, 0x37)
cloneAddress := create2(0, clone, 0x37, salt)
}

// Initialize the clone
Expand Down
7 changes: 6 additions & 1 deletion contracts/protocol/facets/ConfigHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DiamondLib } from "../../diamond/DiamondLib.sol";
import { ProtocolBase } from "../bases/ProtocolBase.sol";
import { ProtocolLib } from "../libs/ProtocolLib.sol";
import { EIP712Lib } from "../libs/EIP712Lib.sol";
import { BeaconClientProxy } from "../../protocol/clients/proxy/BeaconClientProxy.sol";

/**
* @title ConfigHandlerFacet
Expand All @@ -32,10 +33,10 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase {
DiamondLib.addSupportedInterface(type(IBosonConfigHandler).interfaceId);

// Initialize protocol config params
// _addresses.beaconProxy is ignored, since it's deployed later in this function
setTokenAddress(_addresses.token);
setTreasuryAddress(_addresses.treasury);
setVoucherBeaconAddress(_addresses.voucherBeacon);
setBeaconProxyAddress(_addresses.beaconProxy);
setProtocolFeePercentage(_fees.percentage);
setProtocolFeeFlatBoson(_fees.flatBoson);
setMaxEscalationResponsePeriod(_limits.maxEscalationResponsePeriod);
Expand All @@ -62,6 +63,10 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase {
ProtocolLib.ProtocolMetaTxInfo storage pmti = protocolMetaTxInfo();
pmti.domainSeparator = EIP712Lib.buildDomainSeparator(PROTOCOL_NAME, PROTOCOL_VERSION);
pmti.cachedChainId = block.chainid;

// Deploy Boson Voucher proxy contract
address beaconProxy = address(new BeaconClientProxy{ salt: VOUCHER_PROXY_SALT }());
setBeaconProxyAddress(beaconProxy);
}

/**
Expand Down
40 changes: 30 additions & 10 deletions contracts/protocol/facets/ProtocolInitializationHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IBosonProtocolInitializationHandler } from "../../interfaces/handlers/I
import { ProtocolLib } from "../libs/ProtocolLib.sol";
import { ProtocolBase } from "../bases/ProtocolBase.sol";
import { DiamondLib } from "../../diamond/DiamondLib.sol";
import { BeaconClientProxy } from "../../protocol/clients/proxy/BeaconClientProxy.sol";

/**
* @title BosonProtocolInitializationHandler
Expand Down Expand Up @@ -93,11 +94,9 @@ contract ProtocolInitializationHandlerFacet is IBosonProtocolInitializationHandl
if (_isUpgrade) {
if (_version == bytes32("2.2.0")) {
initV2_2_0(_initializationData);
}
if (_version == bytes32("2.2.1")) {
} else if (_version == bytes32("2.2.1")) {
initV2_2_1();
}
if (_version == bytes32("2.3.0")) {
} else if (_version == bytes32("2.3.0")) {
initV2_3_0(_initializationData);
}
}
Expand Down Expand Up @@ -145,20 +144,41 @@ contract ProtocolInitializationHandlerFacet is IBosonProtocolInitializationHandl
* - Current version is not 2.2.1
* - There are already twins. This version adds a new mapping for twins which make it incompatible with previous versions.
* - minResolutionPeriond is not present in _initializationData parameter
* - length of seller creators does not match the length of seller ids
* - if some of seller creators is zero address
* - if some of seller ids does not bellong to a seller
*
* @param _initializationData - data representing uint256 _minResolutionPeriod
* @param _initializationData - data representing uint256 _minResolutionPeriod, uint256[] memory sellerIds, address[] memory sellerCreators
*/
function initV2_3_0(bytes calldata _initializationData) internal {
// Current version must be 2.2.1
require(protocolStatus().version == bytes32("2.2.1"), WRONG_CURRENT_VERSION);

require(protocolCounters().nextTwinId == 1, TWINS_ALREADY_EXIST);

// Decode initialization data
(uint256 _minResolutionPeriod, uint256[] memory sellerIds, address[] memory sellerCreators) = abi.decode(
_initializationData,
(uint256, uint256[], address[])
);

// Initialize limits.maxPremintedVouchers (configHandlerFacet initializer)
uint256 minResolutionPeriod = abi.decode(_initializationData, (uint256));
require(minResolutionPeriod != 0, VALUE_ZERO_NOT_ALLOWED);
protocolLimits().minResolutionPeriod = minResolutionPeriod;
emit MinResolutionPeriodChanged(minResolutionPeriod, msgSender());
require(_minResolutionPeriod != 0, VALUE_ZERO_NOT_ALLOWED);
protocolLimits().minResolutionPeriod = _minResolutionPeriod;
emit MinResolutionPeriodChanged(_minResolutionPeriod, msgSender());

// Initialize sellerCreators
require(sellerIds.length == sellerCreators.length, ARRAY_LENGTH_MISMATCH);
ProtocolLib.ProtocolLookups storage lookups = protocolLookups();
for (uint256 i = 0; i < sellerIds.length; i++) {
(bool exists, , ) = fetchSeller(sellerIds[i]);
require(exists, NO_SUCH_SELLER);
require(sellerCreators[i] != address(0), INVALID_ADDRESS);

lookups.sellerCreator[sellerIds[i]] = sellerCreators[i];
}

// Deploy a new voucher proxy
protocolAddresses().beaconProxy = address(new BeaconClientProxy{ salt: VOUCHER_PROXY_SALT }());
}

/**
Expand Down
12 changes: 10 additions & 2 deletions contracts/protocol/facets/SellerHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -353,16 +353,24 @@ contract SellerHandlerFacet is SellerBase {
string calldata _externalId,
VoucherInitValues calldata _voucherInitValues
) external sellersNotPaused {
ProtocolLib.ProtocolLookups storage lookups = protocolLookups();
address assistant = msgSender();

(bool exists, uint256 sellerId) = getSellerIdByAssistant(assistant);
require(exists, NO_SUCH_SELLER);

Collection[] storage sellersAdditionalCollections = protocolLookups().additionalCollections[sellerId];
Collection[] storage sellersAdditionalCollections = lookups.additionalCollections[sellerId];
uint256 collectionIndex = sellersAdditionalCollections.length + 1; // 0 is reserved for the original collection

// Create clone and store its address to additionalCollections
address voucherCloneAddress = cloneBosonVoucher(sellerId, collectionIndex, assistant, _voucherInitValues);
address voucherCloneAddress = cloneBosonVoucher(
sellerId,
collectionIndex,
lookups.sellerCreator[sellerId],
assistant,
_voucherInitValues,
_externalId
);

// Store collection details
Collection storage newCollection = sellersAdditionalCollections.push();
Expand Down
2 changes: 2 additions & 0 deletions contracts/protocol/libs/ProtocolLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ library ProtocolLib {
mapping(uint256 => mapping(uint256 => uint256)) conditionalCommitsByTokenId;
// seller id => collections
mapping(uint256 => BosonTypes.Collection[]) additionalCollections;
// seller id => address that was used to create it
mapping(uint256 => address) sellerCreator;
}

// Incrementing id counters
Expand Down
1 change: 1 addition & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { task } = require("hardhat/config");
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-web3");
require("hardhat-contract-sizer");
require("hardhat-preprocessor");

const lazyImport = async (module) => {
return await require(module);
Expand Down
6 changes: 0 additions & 6 deletions scripts/deploy-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,6 @@ async function main(env, facetConfig) {
);
await transactionResponse.wait(confirmations);

transactionResponse = await bosonConfigHandler.setBeaconProxyAddress(
await bosonVoucherProxy.getAddress(),
await getFees(maxPriorityFeePerGas)
);
await transactionResponse.wait(confirmations);

// Add NFT auth token addresses to protocol config
// LENS
transactionResponse = await bosonConfigHandler.setAuthTokenContract(
Expand Down
2 changes: 1 addition & 1 deletion scripts/util/deploy-protocol-clients.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async function deployProtocolClients(protocolClientArgs, maxPriorityFeePerGas, i
// Deploy Protocol Client proxy contracts
const protocolClientProxies = await deployProtocolClientProxies(protocolClientBeacons, maxPriorityFeePerGas);

// Cast the proxies to their implementation interfaces
// Cast the proxies to their implementation interfaces ?? ToDo: what is this even needed?
const protocolClients = await castProtocolClientProxies(protocolClientProxies);

return [protocolClientImpls, protocolClientBeacons, protocolClientProxies, protocolClients];
Expand Down
10 changes: 6 additions & 4 deletions scripts/util/estimate-limits.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const {
mockTwin,
accountId,
} = require("../../test/util/mock");
const { setNextBlockTimestamp, getFacetsWithArgs, calculateContractAddress } = require("../../test/util/utils.js");
const { setNextBlockTimestamp, getFacetsWithArgs, calculateCloneAddress } = require("../../test/util/utils.js");

// Common vars
let deployer,
Expand All @@ -54,7 +54,7 @@ let protocolDiamond,
groupHandler,
offerHandler,
twinHandler;
let bosonVoucher;
let bosonVoucher, proxy;
let protocolFeePercentage, protocolFeeFlatBoson, buyerEscalationDepositPercentage;
let handlers = {};
let result = {};
Expand Down Expand Up @@ -788,7 +788,9 @@ setupEnvironment["maxPremintedVouchers"] = async function (tokenCount = 10) {
await offerHandler.connect(sellerWallet1).reserveRange(offer.id, length);

// update bosonVoucher address
handlers.IBosonVoucher = bosonVoucher.attach(calculateContractAddress(await accountHandler.getAddress(), seller1.id));
handlers.IBosonVoucher = bosonVoucher.attach(
calculateCloneAddress(await accountHandler.getAddress(), await proxy.getAddress(), seller1.admin, "")
);

// make an empty array of length tokenCount
const amounts = new Array(tokenCount);
Expand Down Expand Up @@ -944,7 +946,7 @@ async function setupCommonEnvironment() {
const protocolClientArgs = [await protocolDiamond.getAddress()];
const [, beacons, proxies, bv] = await deployProtocolClients(protocolClientArgs, gasLimit);
const [beacon] = beacons;
const [proxy] = proxies;
[proxy] = proxies;
[bosonVoucher] = bv;

// Set protocolFees
Expand Down
16 changes: 14 additions & 2 deletions test/integration/01-update-account-roles-addresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const { RevertReasons } = require("../../scripts/config/revert-reasons.js");
const { oneMonth } = require("../util/constants");
const {
setNextBlockTimestamp,
calculateContractAddress,
calculateCloneAddress,
calculateBosonProxyAddress,
prepareDataSignatureParameters,
applyPercentage,
setupTestEnvironment,
Expand All @@ -35,6 +36,7 @@ describe("[@skip-on-coverage] Update account roles addresses", function () {
let assistant, admin, clerk, treasury, buyer, rando, assistantDR, adminDR, clerkDR, treasuryDR, agent;
let buyerEscalationDepositPercentage, redeemedDate;
let snapshotId;
let beaconProxyAddress;

before(async function () {
accountId.next(true);
Expand All @@ -48,7 +50,9 @@ describe("[@skip-on-coverage] Update account roles addresses", function () {
disputeHandler: "IBosonDisputeHandler",
};

let protocolDiamondAddress;
({
diamondAddress: protocolDiamondAddress,
signers: [admin, treasury, buyer, rando, adminDR, treasuryDR, agent],
contractInstances: { accountHandler, offerHandler, exchangeHandler, fundsHandler, disputeHandler },
protocolConfig: [, , { buyerEscalationDepositPercentage }],
Expand All @@ -59,6 +63,9 @@ describe("[@skip-on-coverage] Update account roles addresses", function () {
assistantDR = adminDR;
clerk = clerkDR = { address: ZeroAddress };

// Get the beacon proxy address
beaconProxyAddress = await calculateBosonProxyAddress(protocolDiamondAddress);

// Get snapshot id
snapshotId = await getSnapshot();
});
Expand All @@ -76,7 +83,12 @@ describe("[@skip-on-coverage] Update account roles addresses", function () {
let expectedCloneAddress, emptyAuthToken, voucherInitValues;

beforeEach(async function () {
expectedCloneAddress = calculateContractAddress(await accountHandler.getAddress(), "1");
expectedCloneAddress = calculateCloneAddress(
await accountHandler.getAddress(),
beaconProxyAddress,
admin.address,
""
);
emptyAuthToken = mockAuthToken();
expect(emptyAuthToken.isValid()).is.true;
voucherInitValues = mockVoucherInitValues();
Expand Down
13 changes: 11 additions & 2 deletions test/integration/04-DR-removes-fees.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const {
const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee");
const {
setNextBlockTimestamp,
calculateContractAddress,
calculateCloneAddress,
calculateBosonProxyAddress,
applyPercentage,
setupTestEnvironment,
getSnapshot,
Expand Down Expand Up @@ -49,7 +50,9 @@ describe("[@skip-on-coverage] DR removes fee", function () {
disputeHandler: "IBosonDisputeHandler",
};

let protocolDiamondAddress;
({
diamondAddress: protocolDiamondAddress,
signers: [admin, treasury, buyer, adminDR, treasuryDR],
contractInstances: { accountHandler, offerHandler, exchangeHandler, fundsHandler, disputeHandler },
protocolConfig: [, , { buyerEscalationDepositPercentage }],
Expand All @@ -60,7 +63,13 @@ describe("[@skip-on-coverage] DR removes fee", function () {
assistantDR = adminDR;
clerk = clerkDR = { address: ZeroAddress };

expectedCloneAddress = calculateContractAddress(await accountHandler.getAddress(), "1");
const beaconProxyAddress = await calculateBosonProxyAddress(protocolDiamondAddress);
expectedCloneAddress = calculateCloneAddress(
await accountHandler.getAddress(),
beaconProxyAddress,
admin.address,
""
);
emptyAuthToken = mockAuthToken();
expect(emptyAuthToken.isValid()).is.true;
voucherInitValues = mockVoucherInitValues();
Expand Down
16 changes: 14 additions & 2 deletions test/protocol/BuyerHandlerTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ const Buyer = require("../../scripts/domain/Buyer");
const { DisputeResolverFee } = require("../../scripts/domain/DisputeResolverFee");
const PausableRegion = require("../../scripts/domain/PausableRegion.js");
const { RevertReasons } = require("../../scripts/config/revert-reasons.js");
const { calculateContractAddress, setupTestEnvironment, getSnapshot, revertToSnapshot } = require("../util/utils.js");
const {
calculateCloneAddress,
calculateBosonProxyAddress,
setupTestEnvironment,
getSnapshot,
revertToSnapshot,
} = require("../util/utils.js");
const {
mockOffer,
mockSeller,
Expand Down Expand Up @@ -400,7 +406,13 @@ describe("BuyerHandler", function () {
.connect(other1)
.commitToOffer(await other1.getAddress(), offerId, { value: offer.price });

const bosonVoucherCloneAddress = calculateContractAddress(await exchangeHandler.getAddress(), "1");
const beaconProxyAddress = await calculateBosonProxyAddress(await accountHandler.getAddress());
const bosonVoucherCloneAddress = calculateCloneAddress(
await accountHandler.getAddress(),
beaconProxyAddress,
admin.address,
""
);
bosonVoucher = await getContractAt("IBosonVoucher", bosonVoucherCloneAddress);
const balance = await bosonVoucher.connect(rando).balanceOf(await other1.getAddress());
expect(balance).equal(1);
Expand Down
Loading

0 comments on commit c6d0e08

Please sign in to comment.