Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: generic UP and Owner Deployer #4

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions contracts/FirstOwnerDelegator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import { UniversalProfile } from '@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol';

contract FirstOwnerDelegator is UniversalProfile {

address private _keyManager;
address thisContract = address(this);

constructor() UniversalProfile(address(0)) {

}

function _executeCall(address, uint256, bytes memory) internal pure override returns (bytes memory) {
revert("FirstOwnerDelegator: selfdestruct is not allowed");
}


function giveOwnershipToKeyManager(bytes32[] memory keys_, bytes[] memory values_) external {
if(thisContract == address(this)) {
revert("FirstOwnerDelegator: only delegate calls are allowed");
}

_setOwner(_keyManager);
for (uint256 i = 0; i < keys_.length; i++) {
_setData(keys_[i], values_[i]);
}
}


function setUpUniversalProfile(bytes calldata initializationBytes) public {
bytes memory datas = initializationBytes[0: initializationBytes.length - 40];
address universalProfile = address(bytes20(initializationBytes[initializationBytes.length - 40: initializationBytes.length - 20]));
address keyManager = address(bytes20(initializationBytes[initializationBytes.length - 20: initializationBytes.length]));
_keyManager = keyManager;

(bool success,) = universalProfile.call(abi.encodeWithSignature("execute(uint256,address,uint256,bytes)",4, address(this),0,datas));
require(success, "FirstOwnerDelegator: execute call failed");
}


}
161 changes: 161 additions & 0 deletions contracts/UniversalProfileAndOwnerDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {_LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, _PERMISSION_CHANGEOWNER, _PERMISSION_EDITPERMISSIONS, ALL_REGULAR_PERMISSIONS} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6Constants.sol";
import {LSP6Utils} from '@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6Utils.sol';
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ILSP14Ownable2Step} from '@lukso/lsp-smart-contracts/contracts/LSP14Ownable2Step/ILSP14Ownable2Step.sol';




// 1st step: deploy the universal profile contract

// 2nd step: deploy the owner contract (the owner that will be set as the owner of the universal profile contract at the end of the deployment process)

// 3rd step: call firstOwner that will be in charge of making the necessary calls that could be needed to set up the universal profile contract
// in this example FirstOwner will be in charge of setting up the permissions between the key manager and the UP etc... (we could use delegatecall to do this)

// TODO: deal with msg.value logic

contract UniversalProfileAndOwnerDeployer {

struct UniversalProfileDeployment {
uint256 value;
bytes32 salt;
bytes byteCode;
}

struct OwnerDeployment {
uint256 value;
bytes32 salt;
bytes byteCode; // owner contract bytecode + constructor params to be appended to the constructor before the universal profile address
bool appendUniversalProfileAddress; // will append the universal profile address to the constructor params if true + the extraConstructorParams
bytes extraConstructorParams; // params to be appended to the constructor after the universal profile address
}

struct UniversalProfileDeploymentInit {
uint256 value;
bytes32 salt;
address implementation;
bytes initializationBytes;
}

struct OwnerDeploymentInit {
uint256 value;
bytes32 salt;
address implementation;
bool appendUniversalProfileAddress; // will append the universal profile address to the initialisation bytes if true + the extraConstructorParams
bytes initializationBytes;
bytes extraInitialisationBytes; // params to be appended to the constructor after the universal profile address
}


function deployCreate2ProxyInit(
UniversalProfileDeploymentInit calldata universalProfileDeployment,
OwnerDeploymentInit calldata ownerDeployment,
address universalProfileFirstOwner,
bytes calldata calldaToFirstOwner
) public payable virtual returns (address upProxy, address ownerProxy) {

if(msg.value < universalProfileDeployment.value + ownerDeployment.value ) {
revert("UniversalProfileDeployer: insufficient funds");
}

bytes32 universalProfileGeneratedSalt = keccak256(abi.encode(universalProfileDeployment.salt,ownerDeployment,universalProfileFirstOwner, calldaToFirstOwner));


upProxy = Clones.cloneDeterministic(universalProfileDeployment.implementation, universalProfileGeneratedSalt);

(bool success,) = upProxy.call{value: msg.value}(universalProfileDeployment.initializationBytes);
require(success, 'failed initialization of UP proxy');

ownerProxy = Clones.cloneDeterministic(ownerDeployment.implementation, keccak256(abi.encode(ownerDeployment.salt, upProxy)));

bytes memory ownerInitializationBytes = ownerDeployment.appendUniversalProfileAddress ? abi.encodePacked(ownerDeployment.initializationBytes, abi.encode(upProxy)) : ownerDeployment.initializationBytes;

(success,) = ownerProxy.call{value: msg.value}(ownerInitializationBytes);
require(success, 'failed initialization of owner proxy');

(success,) = universalProfileFirstOwner.call{value: msg.value}(abi.encodeWithSignature("setUpUniversalProfile(bytes)", abi.encodePacked(calldaToFirstOwner, upProxy, ownerProxy)));
require(success, "UniversalProfileDeployer: first owner call failed");

}


function deployUniversalProfileAndOwner(UniversalProfileDeployment calldata universalProfileDeployment, OwnerDeployment calldata ownerDeployment, address universalProfileFirstOwner, bytes calldata calldaToFirstOwner)
public
payable
returns (address universalProfile, address owner )
{
if(msg.value < universalProfileDeployment.value + ownerDeployment.value ) {
revert("UniversalProfileDeployer: insufficient funds");
}

bytes32 universalProfileGeneratedSalt = keccak256(abi.encode(universalProfileDeployment.salt,ownerDeployment,universalProfileFirstOwner, calldaToFirstOwner));

universalProfile = Create2.deploy(universalProfileDeployment.value, universalProfileGeneratedSalt, abi.encodePacked(universalProfileDeployment.byteCode, abi.encode(universalProfileFirstOwner)));

// if appendUniversalProfileAddress is true, the universal profile address + extraConstructorParams will be appended to the constructor params
bytes memory ownerByteCode = ownerDeployment.appendUniversalProfileAddress ? abi.encodePacked(ownerDeployment.byteCode, abi.encode(universalProfile),ownerDeployment.extraConstructorParams) : ownerDeployment.byteCode;

// here owner refers as the future owner of the UP at the end of the transaction
owner = Create2.deploy(ownerDeployment.value, ownerDeployment.salt, ownerByteCode);

uint256 totalValueSent = universalProfileDeployment.value + ownerDeployment.value ;

(bool success,) = universalProfileFirstOwner.call{value: msg.value - totalValueSent}(abi.encodeWithSignature("setUpUniversalProfile(bytes)", abi.encodePacked(calldaToFirstOwner, universalProfile, owner)));
require(success, "UniversalProfileDeployer: first owner call failed");
}

}

interface IFirstOwner {
function setUpUniversalProfile(bytes calldata initializationBytes) external;
}

contract FirstOwner {

function setUpUniversalProfile(bytes calldata initializationBytes) public payable {
address allPermissionsAddress = address(bytes20(initializationBytes));
address universalProfileAddress = address(bytes20(initializationBytes[20:40]));
address keyManagerAddress = address(bytes20(initializationBytes[40:]));

// calculate deployer permissions
bytes32[] memory deployerPermissionsArray = new bytes32[](2);
deployerPermissionsArray[0] = _PERMISSION_CHANGEOWNER;
deployerPermissionsArray[1] = _PERMISSION_EDITPERMISSIONS;
bytes32 deployerPermissions = LSP6Utils.combinePermissions(deployerPermissionsArray);

// setDataBatch keys
bytes32 deloyerKey = bytes32(abi.encodePacked(_LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, bytes2(0), address(this)));
bytes32 allPermissionsKey = bytes32(abi.encodePacked(_LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, bytes2(0), allPermissionsAddress));
bytes32[] memory keys = new bytes32[](2);
keys[0] = deloyerKey;
keys[1] = allPermissionsKey;

// setDataBach values
bytes[] memory values = new bytes[](2);
values[0] = abi.encodePacked(deployerPermissions);
values[1] = abi.encodePacked(ALL_REGULAR_PERMISSIONS);

// setDataBatch on UP contract
(bool success, ) = universalProfileAddress.call(abi.encodeWithSignature("setDataBatch(bytes32[],bytes[])", keys, values));
require(success, "setDataBatch failed");

// transferOwnership on UP contract
(bool successTransfer, ) = universalProfileAddress.call(abi.encodeWithSignature("transferOwnership(address)", keyManagerAddress));
require(successTransfer, "transferOwnership failed");

// acceptOwnership on keyManager contract
bytes memory acceptOwnershipBytes = abi.encodeWithSignature("acceptOwnership()");
(bool successAccept,) = keyManagerAddress.call(abi.encodeWithSignature("execute(bytes)", acceptOwnershipBytes));
require(successAccept, "acceptOwnership failed");

// setData on keyManager contract
bytes memory setDataBytes = abi.encodeWithSignature("setData(bytes32,bytes)", deloyerKey, "");
(bool successSetData,) = keyManagerAddress.call(abi.encodeWithSignature("execute(bytes)", setDataBytes));
require(successSetData, "setData failed");
}
}
1 change: 0 additions & 1 deletion contracts/UniversalProfileDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ contract UniversalProfileDeployer {
function deployUPAndKeyManager(bytes calldata universalProfileByteCode, bytes calldata keyManagerByteCode, address allPermissionsAddress, bytes32 universalProfleProvidedSalt, bytes32 keyManagerProvidedSalt)
public
payable
virtual
returns (address universalProfile, address keyManager)
{ // generate salt for the UP contract
bytes32 universalProfileGeneratedSalt = keccak256(abi.encodePacked(allPermissionsAddress, keyManagerByteCode, universalProfleProvidedSalt));
Expand Down
21 changes: 21 additions & 0 deletions scripts/UPDeployer/deployFirstOwnerDelegator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ethers } from "hardhat";

async function main() {
const gasPrice = await ethers.provider.getGasPrice();

const FirstOwnerDelegatorFactory = await ethers.getContractFactory(
"FirstOwnerDelegator"
);
const firstOwnerDelegator = await FirstOwnerDelegatorFactory.deploy({
gasPrice: gasPrice.add(ethers.utils.parseUnits("5", "gwei")),
});

await firstOwnerDelegator.deployed();

console.log("firstOwnerDelegator", firstOwnerDelegator.address);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
90 changes: 90 additions & 0 deletions scripts/UPDeployer/deployUPThroughDelegator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { ethers } from "hardhat";
import { bytecode as universalProfileByteCode } from "../../artifacts/@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol/UniversalProfile.json";
import { bytecode as ownerBytecode } from "../../artifacts/@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol/LSP6KeyManager.json";

const UP_AND_OWNER_DEPLOYER_ADDRESS =
"0x37d586a523B8953c37ffA019e8748CDcb416bBd3";

const ALL_PERMISSIONS_SIGNER = "0x0eCC079C20DaA9fDE0e26b6d745c0b38479ff200";

const UNIVERSAL_PROFILE_FIRST_OWNER_ADDRESS =
"0xf7E8f56a487F0204856E54789E6635470D3A2ca9";

const UNIVERSAL_PROFILE_DEPLOYMENT = {
value: 0,
salt: ethers.utils.keccak256(ethers.utils.toUtf8Bytes("universalprofile")),
byteCode: universalProfileByteCode,
};

const OWNER_DEPLOYMENT = {
value: 0,
salt: ethers.utils.keccak256(ethers.utils.toUtf8Bytes("owner")),
byteCode: ownerBytecode,
appendUniversalProfileAddress: true,
extraConstructorParams: "0x",
};

async function main() {
const universalprofileDeployer = await ethers.getContractAt(
"UniversalProfileAndOwnerDeployer",
UP_AND_OWNER_DEPLOYER_ADDRESS
);

const FirstOwnerDelegatorFactory = await ethers.getContractFactory(
"FirstOwnerDelegator"
);

const allPermissionsSignerPermissionsKey =
"0x4b80742de2bf82acb3630000" + ALL_PERMISSIONS_SIGNER.slice(2);

const allPermissionsSignerPermissionsValue =
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";

const firstOwnerDelegatorBytes =
FirstOwnerDelegatorFactory.interface.encodeFunctionData(
"giveOwnershipToKeyManager",
[
[allPermissionsSignerPermissionsKey],
[allPermissionsSignerPermissionsValue],
]
);

const [universalprofile, owner] =
await universalprofileDeployer.callStatic.deployUniversalProfileAndOwner(
UNIVERSAL_PROFILE_DEPLOYMENT,
OWNER_DEPLOYMENT,
UNIVERSAL_PROFILE_FIRST_OWNER_ADDRESS,
firstOwnerDelegatorBytes
);
console.log("universalprofile", universalprofile);
console.log("owner", owner);

const tx = await universalprofileDeployer.deployUniversalProfileAndOwner(
UNIVERSAL_PROFILE_DEPLOYMENT,
OWNER_DEPLOYMENT,
UNIVERSAL_PROFILE_FIRST_OWNER_ADDRESS,
firstOwnerDelegatorBytes,
{
gasPrice: (
await ethers.provider.getGasPrice()
).add(ethers.utils.parseUnits("1", "gwei")),
}
);

await tx.wait();
console.log("tx hash", tx.hash);

const universalprofileContract = await ethers.getContractAt(
"UniversalProfile",
universalprofile
);
const allPermissionsPermissions = await universalprofileContract.getData(
allPermissionsSignerPermissionsKey
);
console.log("allPermissionsPermissions", allPermissionsPermissions);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
60 changes: 60 additions & 0 deletions scripts/UPDeployer/deployUPThroughUPAndOwnerDeployer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ethers } from "hardhat";
import { bytecode as universalProfileByteCode } from "../../artifacts/@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol/UniversalProfile.json";
import { bytecode as ownerBytecode } from "../../artifacts/@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol/LSP6KeyManager.json";

const UP_AND_OWNER_DEPLOYER_ADDRESS =
"0x37d586a523B8953c37ffA019e8748CDcb416bBd3";
const ALL_PERMISSIONS_SIGNER = "0x0eCC079C20DaA9fDE0e26b6d745c0b38479ff200";
const UNIVERSAL_PROFILE_FIRST_OWNER_ADDRESS =
"0x50b1950b351F8422fdEA736Bf262164B618E69eF";

const UNIVERSAL_PROFILE_DEPLOYMENT = {
value: 0,
salt: ethers.utils.keccak256(ethers.utils.toUtf8Bytes("universalprofile")),
byteCode: universalProfileByteCode,
};

const OWNER_DEPLOYMENT = {
value: 0,
salt: ethers.utils.keccak256(ethers.utils.toUtf8Bytes("owner")),
byteCode: ownerBytecode,
appendUniversalProfileAddress: true,
extraConstructorParams: "0x",
};

async function main() {
const universalprofileDeployer = await ethers.getContractAt(
"UniversalProfileAndOwnerDeployer",
UP_AND_OWNER_DEPLOYER_ADDRESS
);

const [universalprofile, owner] =
await universalprofileDeployer.callStatic.deployUniversalProfileAndOwner(
UNIVERSAL_PROFILE_DEPLOYMENT,
OWNER_DEPLOYMENT,
UNIVERSAL_PROFILE_FIRST_OWNER_ADDRESS,
ALL_PERMISSIONS_SIGNER
);
console.log("universalprofile", universalprofile);
console.log("owner", owner);

const tx = await universalprofileDeployer.deployUniversalProfileAndOwner(
UNIVERSAL_PROFILE_DEPLOYMENT,
OWNER_DEPLOYMENT,
UNIVERSAL_PROFILE_FIRST_OWNER_ADDRESS,
ALL_PERMISSIONS_SIGNER,
{
gasPrice: (
await ethers.provider.getGasPrice()
).add(ethers.utils.parseUnits("1", "gwei")),
}
);

await tx.wait();
console.log("tx hash", tx.hash);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Loading