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

blast: v.1.5.3 for Blast #37

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
20 changes: 20 additions & 0 deletions contracts/v1.5.x/BlastConstant.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;

// blast yield contract
IBlast constant BLAST = IBlast(0x4300000000000000000000000000000000000002);
// BlastGasCollector contract using Create3Factory to generate constant contract address
address constant GAS_COLLECTOR = 0xBd9D6d96b21d679983Af4ed6182Fd9fff0031eA4;

interface IBlast {
// see https://docs.blast.io/building/guides/gas-fees
function configureAutomaticYield() external;
function configureClaimableGas() external;
function configureGovernor(address governor) external;
function configureGovernorOnBehalf(address _newGovernor, address contractAddress) external;
function claimAllGas(address contractAddress, address recipient) external returns (uint256);
}

interface IBlastPoints {
function configurePointsOperator(address operator) external;
}
64 changes: 64 additions & 0 deletions contracts/v1.5.x/BlastGasCollector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import {BLAST} from "./BlastConstant.sol";

contract BlastGasCollector is AccessControl {
/// @notice gas collector role for using claimGas()
bytes32 public constant GAS_COLLECTOR_ROLE = keccak256("GAS_COLLECTOR_ROLE");

/// @notice constructor setting owner and configure BLAST
constructor(address _admin) {
// contract balance will grow automatically
BLAST.configureAutomaticYield();
// let GAS_COLLECTOR collect gas
BLAST.configureClaimableGas();
// sender as default admin
_setupRole(DEFAULT_ADMIN_ROLE, _admin);
}

/// @notice can claim gas including self gas
/// @param target claim target
/// @param recipientOfGas claimed gas recipientOfGas
function claimGas(address target, address recipientOfGas) external {
require(hasRole(GAS_COLLECTOR_ROLE, msg.sender), "caller is not gas collecotr role");
BLAST.claimAllGas(target, recipientOfGas);
}

/// @notice can claim gas including self gas
/// @param targetAry claim target
/// @param recipientOfGas claimed gas recipientOfGas
function claimGasBatch(address[] calldata targetAry, address recipientOfGas) external {
require(hasRole(GAS_COLLECTOR_ROLE, msg.sender), "caller is not gas collecotr role");
for (uint256 i = 0; i < targetAry.length; i++) {
BLAST.claimAllGas(targetAry[i], recipientOfGas);
}
}

/// @notice configures the governor for a specific contract. Called by an authorized user
/// @param _newGovernor the address of new governor
/// @param target the address of the contract to be configured
function configureGovernorOnBehalf(address _newGovernor, address target) external {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "caller is not admin");
BLAST.configureGovernorOnBehalf(_newGovernor, target);
}

/// @notice configures the governor for a specific contract. Called by an authorized user
/// @param _newGovernor the address of new governor
/// @param targetAry the address of the contract to be configured
function configureGovernorOnBehalf(address _newGovernor, address[] calldata targetAry) external {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "caller is not admin");
for (uint256 i = 0; i < targetAry.length; i++) {
BLAST.configureGovernorOnBehalf(_newGovernor, targetAry[i]);
}
}

/// @notice selfdestruct to transfer all funds to the owner
function destruct() external {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "caller is not admin");
selfdestruct(payable(msg.sender));
}
}
34 changes: 19 additions & 15 deletions contracts/v1.5.x/BloctoAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "@account-abstraction/contracts/core/BaseAccount.sol";

import "../utils/TokenCallbackHandler.sol";
import "./CoreWallet.sol";
import {BLAST, IBlastPoints, GAS_COLLECTOR} from "./BlastConstant.sol";

/**
* Blocto account.
Expand All @@ -24,15 +25,19 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas
/// @notice entrypoint from 4337 official
IEntryPoint private immutable _entryPoint;

/// @notice blast points contract address, testnet address DIFF from mainnet
IBlastPoints public immutable _blastPoints;

/// @notice initialized _IMPLEMENTATION_SLOT
bool public initializedImplementation = false;

/**
* constructor for BloctoAccount
* @param anEntryPoint entrypoint address
*/
constructor(IEntryPoint anEntryPoint) {
constructor(IEntryPoint anEntryPoint, address blastPointsAddr) {
_entryPoint = anEntryPoint;
_blastPoints = IBlastPoints(blastPointsAddr);
}

/**
Expand Down Expand Up @@ -111,20 +116,6 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas
return 0;
}

/**
* check current account deposit in the entryPoint StakeManager
*/
function getDeposit() public view returns (uint256) {
return entryPoint().balanceOf(address(this));
}

/**
* deposit more funds for this account in the entryPoint StakeManager
*/
function addDeposit() public payable {
entryPoint().depositTo{value: msg.value}(address(this));
}

/**
* withdraw deposit to withdrawAddress from entryPoint StakeManager
* @param withdrawAddress target to send to
Expand All @@ -147,4 +138,17 @@ contract BloctoAccount is UUPSUpgradeable, TokenCallbackHandler, CoreWallet, Bas
require(Address.isContract(implementation), "ERC1967: new implementation is not a contract");
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = implementation;
}

/// @notice configure blast for yield, gas, and points
/// @param pointsOperator blast points contract operator address, should be EOA from https://docs.blast.io/airdrop/api#configuring-a-points-operator
function configureBlast(address pointsOperator) external {
require(!initialized, "blast: must not be initialized");
// contract balance will grow automatically
BLAST.configureAutomaticYield();
// let GAS_COLLECTOR collect gas
BLAST.configureClaimableGas();
BLAST.configureGovernor(GAS_COLLECTOR);
// operator should be EOA
_blastPoints.configurePointsOperator(pointsOperator);
}
}
3 changes: 2 additions & 1 deletion contracts/v1.5.x/BloctoAccountCloneableWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import "./BloctoAccount.sol";
contract BloctoAccountCloneableWallet is BloctoAccount {
/// @notice constructor that deploys a NON-FUNCTIONAL version of `BloctoAccount`
/// @param anEntryPoint entrypoint address
constructor(IEntryPoint anEntryPoint) BloctoAccount(anEntryPoint) {
/// @param blastPointsAddr blast points contract address
constructor(IEntryPoint anEntryPoint, address blastPointsAddr) BloctoAccount(anEntryPoint, blastPointsAddr) {
initialized = true;
initializedImplementation = true;
}
Expand Down
8 changes: 8 additions & 0 deletions contracts/v1.5.x/BloctoAccountFactoryBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "../BloctoAccountProxy.sol";
import "./BloctoAccount.sol";

import {BLAST, GAS_COLLECTOR} from "./BlastConstant.sol";

// BloctoAccountFactory for creating BloctoAccountProxy
contract BloctoAccountFactoryBase is Initializable, AccessControlUpgradeable {
/// @notice create account role for using createAccount() and createAccount2()
Expand Down Expand Up @@ -49,6 +51,12 @@ contract BloctoAccountFactoryBase is Initializable, AccessControlUpgradeable {
bloctoAccountImplementation = _bloctoAccountImplementation;
entryPoint = _entryPoint;
_setupRole(DEFAULT_ADMIN_ROLE, _admin);

// contract balance will grow automatically
BLAST.configureAutomaticYield();
// let GAS_COLLECTOR collect gas
BLAST.configureClaimableGas();
BLAST.configureGovernor(GAS_COLLECTOR);
}

/// @notice only the admin can update admin functioins
Expand Down
4 changes: 4 additions & 0 deletions contracts/v1.5.x/BloctoAccountFactoryV1_5_2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ contract BloctoAccountFactoryV1_5_2 is BloctoAccountFactoryBase {
);
ret = BloctoAccount(payable(newProxy));
ret.initImplementation(bloctoAccountImplementation);
ret.configureBlast(msg.sender);
ret.init(
_authorizedAddress, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey
);
Expand Down Expand Up @@ -55,6 +56,7 @@ contract BloctoAccountFactoryV1_5_2 is BloctoAccountFactoryBase {
);
ret = BloctoAccount(payable(newProxy));
ret.initImplementation(bloctoAccountImplementation);
ret.configureBlast(msg.sender);
ret.init2(
_authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParitys, _mergedKeys
);
Expand Down Expand Up @@ -98,6 +100,7 @@ contract BloctoAccountFactoryV1_5_2 is BloctoAccountFactoryBase {
Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation))));
ret = BloctoAccount(payable(newProxy));
ret.initImplementation(bloctoAccountImplementation151Plus);
ret.configureBlast(msg.sender);
ret.init(
_authorizedAddress, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey
);
Expand All @@ -124,6 +127,7 @@ contract BloctoAccountFactoryV1_5_2 is BloctoAccountFactoryBase {
Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation))));
ret = BloctoAccount(payable(newProxy));
ret.initImplementation(bloctoAccountImplementation151Plus);
ret.configureBlast(msg.sender);
ret.init2(
_authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParitys, _mergedKeys
);
Expand Down
2 changes: 2 additions & 0 deletions contracts/v1.5.x/BloctoAccountFactoryV1_5_3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ contract BloctoAccountFactoryV1_5_3 is BloctoAccountFactoryBase {
Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation))));
ret = BloctoAccount(payable(newProxy));
ret.initImplementation(bloctoAccountImplementation_1_5_3);
ret.configureBlast(msg.sender);
ret.init(
_authorizedAddress, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParity, _mergedKey
);
Expand All @@ -58,6 +59,7 @@ contract BloctoAccountFactoryV1_5_3 is BloctoAccountFactoryBase {
Create2.deploy(0, _salt, abi.encodePacked(BLOCTO_ACCOUNT_PROXY, abi.encode(address(initImplementation))));
ret = BloctoAccount(payable(newProxy));
ret.initImplementation(bloctoAccountImplementation_1_5_3);
ret.configureBlast(msg.sender);
ret.init2(
_authorizedAddresses, uint256(uint160(_cosigner)), _recoveryAddress, _mergedKeyIndexWithParitys, _mergedKeys
);
Expand Down
1 change: 0 additions & 1 deletion contracts/v1.5.x/CoreWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ contract CoreWallet is IERC1271 {
require(_authorizedAddress != _recoveryAddress, "do not use the recovery address as an authorized address");
authorizations[AUTH_VERSION_INCREMENTOR + uint256(uint160(_authorizedAddress))] = _cosigner;
mergedKeys[AUTH_VERSION_INCREMENTOR + _mergedKeyIndexWithParitys[i]] = _mergedKeys[i];

emit Authorized(_authorizedAddress, _cosigner);
}
}
Expand Down
28 changes: 20 additions & 8 deletions deploy/1_0_deploy_account_accountFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ import { hexZeroPad } from '@ethersproject/bytes'
const EntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'
// NOTE: don't forget to change this according to the backend deploy account
// prod mainnet
const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29'
const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054'
// const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29'
// const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054'

// dev testnet
// const CreateAccountBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62'
// const Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b'
const CreateAccountBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62'
const Create3FactoryAddress = '0xd6CA621705575c3c23622b0802964a556870953b'

// BloctowalletCloneableSalt
const BloctoAccountCloneableWalletSalt = 'BloctoAccount_v140'
const BloctoAccountFactorySalt = 'BloctoAccountFactoryProxy_v140'

async function getBlastPointAddress (): Promise<string> {
const { chainId } = await ethers.provider.getNetwork()
// 81457: mainnet using 0x2536FE9ab3F511540F2f9e2eC2A805005C3Dd800 from https://docs.blast.io/airdrop/api#configuring-a-points-operator
return chainId === 81457 ? '0x2536FE9ab3F511540F2f9e2eC2A805005C3Dd800' : '0x2fc95838c71e76ec69ff817983BFf17c710F34E0'
}

async function main (): Promise<void> {
// const lockedAmount = ethers.utils.parseEther("1");
const [owner] = await ethers.getSigners()
Expand All @@ -34,11 +40,14 @@ async function main (): Promise<void> {
console.log(`Deploying BloctoAccountCloneableWallet with -> \n\t salt str: ${BloctoAccountCloneableWalletSalt}`)
const walletCloneable = await create3Factory.getDeployed(owner.address, accountSalt)

const blastPointAddress = await getBlastPointAddress()
console.log('Using blastPointAddress: ', blastPointAddress)

if ((await ethers.provider.getCode(walletCloneable)) === '0x') {
console.log(`BloctowalletCloneableWallet deploying to: ${walletCloneable}`)
const tx = await create3Factory.deploy(
accountSalt,
getDeployCode(new BloctoAccountCloneableWallet__factory(), [EntryPoint]))
getDeployCode(new BloctoAccountCloneableWallet__factory(), [EntryPoint, blastPointAddress]))
await tx.wait()

console.log(`BloctowalletCloneableWallet JUST deployed to: ${walletCloneable}`)
Expand All @@ -54,7 +63,7 @@ async function main (): Promise<void> {
const BloctoAccountFactory = await ethers.getContractFactory('BloctoAccountFactory')
const accountFactory = await create3DeployTransparentProxy(BloctoAccountFactory,
[walletCloneable, EntryPoint, owner.address],
{ initializer: 'initialize' }, create3Factory, owner, accountFactorySalt)
{ initializer: 'initialize', constructorArgs: [walletCloneable], unsafeAllow: ['constructor', 'state-variable-immutable'] }, create3Factory, owner, accountFactorySalt)

await accountFactory.deployed()
console.log(`BloctoAccountFactory JUST deployed to: ${accountFactory.address}`)
Expand Down Expand Up @@ -86,15 +95,18 @@ async function main (): Promise<void> {
address: walletCloneable,
contract: 'contracts/v1.5.x/BloctoAccountCloneableWallet.sol:BloctoAccountCloneableWallet',
constructorArguments: [
EntryPoint
EntryPoint, blastPointAddress
]
})

// verify BloctoAccountFactory (if proxy)
const accountFactoryImplAddress = await getImplementationAddress(ethers.provider, accountFactoryAddr)
await hre.run('verify:verify', {
address: accountFactoryImplAddress,
contract: 'contracts/v1.5.x/BloctoAccountFactory.sol:BloctoAccountFactory'
contract: 'contracts/v1.5.x/BloctoAccountFactory.sol:BloctoAccountFactory',
constructorArguments: [
walletCloneable
]
})
}

Expand Down
2 changes: 1 addition & 1 deletion deploy/3_2_createMultipleSchnorrAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function main (): Promise<void> {
console.log('ethersSigner address: ', await ethersSigner.getAddress())
console.log('factory.address', factory.address)

const tx = await factory.createAccount2([authorizedWallet.address, authorizedWallet2.address, cosignerWallet.address],
const tx = await factory.createAccount2_1_5_3([authorizedWallet.address, authorizedWallet2.address, cosignerWallet.address],
cosignerWallet.address, RecoverAddress,
SALT, // random salt
[pxIndexWithParity, pxIndexWithParity2, pxIndexWithParity3],
Expand Down
66 changes: 66 additions & 0 deletions deploy/6_deploy_BlastGasCollector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import hre, { ethers } from 'hardhat'
import { getDeployCode } from '../src/create3Factory'
import {
BlastGasCollector__factory,
CREATE3Factory__factory
} from '../typechain'
import { hexZeroPad } from '@ethersproject/bytes'

// prod mainnet
// const CreateAccountBackend = '0x8A6a17F1A3DA0F407A67BF8E076Ed7F678D85f29'
// dev testnet
const GasCollectorBackend = '0x67465ec61c3c07b119e09fbb4a0b59eb1ba14e62'

// create3Factory
const Create3FactoryAddress = '0x2f06F83f960ea999536f94df279815F79EeB4054'

// BloctocontractInstanceSalt
const BlastGasCollectorSalt = 'BlastGasCollector_v1.0'

async function main (): Promise<void> {
// const lockedAmount = ethers.utils.parseEther("1");
const [owner] = await ethers.getSigners()
console.log('deploy with account: ', owner.address)

const create3Factory = CREATE3Factory__factory.connect(Create3FactoryAddress, owner)
// -------------------BlastGasCollector------------------------------//
const contractSalt = hexZeroPad(Buffer.from(BlastGasCollectorSalt, 'utf-8'), 32)
console.log(`Deploying BlastGasCollector with -> \n\t salt str: ${BlastGasCollectorSalt}`)
const contractInstance = await create3Factory.getDeployed(owner.address, contractSalt)

if ((await ethers.provider.getCode(contractInstance)) === '0x') {
console.log(`BlastGasCollector deploying to: ${contractInstance}`)
const tx = await create3Factory.deploy(
contractSalt,
getDeployCode(new BlastGasCollector__factory(), [owner.address]))
await tx.wait()
console.log(`BlastGasCollector JUST deployed to: ${contractInstance}`)
console.log('Granting gas collector role to backend address: ', GasCollectorBackend)

const gasCollector = BlastGasCollector__factory.connect(contractInstance, owner)
await gasCollector.grantRole(await gasCollector.GAS_COLLECTOR_ROLE(), GasCollectorBackend)
} else {
console.log(`BlastGasCollector WAS deployed to: ${contractInstance}`)
}

// sleep 10 seconds
console.log('sleep 10 seconds for chain sync...')
await new Promise(f => setTimeout(f, 10000))

// -------------------Verify------------------------------//
// verify BlastGasCollector
await hre.run('verify:verify', {
address: contractInstance,
contract: 'contracts/v1.5.x/BlastGasCollector.sol:BlastGasCollector',
constructorArguments: [
owner.address
]
})
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
Loading