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(relay-kit): make gas limit optional #414

Merged
merged 43 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
74aed4e
create first experimental base gas
dasanra Apr 17, 2023
1a3e579
Type uint256 using strings
dasanra Apr 17, 2023
be68c5b
fix tests
dasanra Apr 17, 2023
1c91559
keep nonce as number
dasanra Apr 18, 2023
852ee99
fix tests
dasanra Apr 18, 2023
87d00f7
fix tests
dasanra Apr 18, 2023
30b0a07
fix tests
dasanra Apr 18, 2023
d44f536
Remove unused toString()
dasanra Apr 18, 2023
eac95b7
rollback bignumberish to number | string to increase web3 compatibility
dasanra Apr 19, 2023
c64b316
update response transaction types
dasanra Apr 19, 2023
84f0a4c
Merge branch 'development' into type-string-uint256
dasanra Apr 19, 2023
db801bd
Merge branch 'development' into make-gas-limit-optional
dasanra Apr 19, 2023
7c10192
Merge branch 'development' into type-string-uint256
dasanra Apr 19, 2023
83d6583
chore test naming
dasanra Apr 19, 2023
bddddd8
Merge branch 'type-string-uint256' into make-gas-limit-optional
dasanra Apr 19, 2023
aafc1cd
Merge branch 'development' into make-gas-limit-optional
dasanra Apr 20, 2023
68007e2
revert type regression
dasanra Apr 20, 2023
6861140
Merge branch 'development' into make-gas-limit-optional
DaniSomoza May 31, 2023
003d9fe
Add singleton to encode execTransaction data
DaniSomoza Jun 2, 2023
640d49c
Added GasLimit as optional and compatible with the conterfactual depl…
DaniSomoza Jul 3, 2023
5ca0761
Added onlyCalls prop in createTransaction in the relay-kit
DaniSomoza Jul 4, 2023
3670bc1
Add SimulateTxAccessor Contract
DaniSomoza Jul 11, 2023
06d21d2
Add estimateSafeTxGasWithSimulate function
DaniSomoza Jul 12, 2023
e86fdf6
Merge branch 'development' into make-gas-limit-optional
DaniSomoza Jul 12, 2023
25184f6
Merge branch 'development' into make-gas-limit-optional
DaniSomoza Jul 12, 2023
025e73d
Update safe-deployment v1.26.0
germartinez Jul 13, 2023
9ef742f
Update deps
germartinez Jul 13, 2023
60966bb
Add new networks
germartinez Jul 13, 2023
8b5277a
Update yarn.lock
germartinez Jul 13, 2023
b3ed718
Fix yarn.lock
germartinez Jul 13, 2023
7e5adf6
Merge branch 'safe-deployments-1_26_0' into make-gas-limit-optional
DaniSomoza Jul 13, 2023
bd51056
Add decodeSafeTxGas function
DaniSomoza Jul 13, 2023
4199edb
Set default Safe version to 1.3.0
germartinez Jul 13, 2023
94fcfb4
Update default version in READMEs
germartinez Jul 13, 2023
1801f06
Merge branch 'default_1_3_0' into make-gas-limit-optional
DaniSomoza Jul 13, 2023
9202697
Merge branch 'development' into make-gas-limit-optional
DaniSomoza Jul 13, 2023
729e324
Add options to executeRelayTransaction
DaniSomoza Jul 19, 2023
ff343e4
Add to the safeTxGas estimation a security margin factor
DaniSomoza Jul 20, 2023
60d5c16
Add check token selected for gas payment is compatible
DaniSomoza Jul 26, 2023
b175a64
restores SimulateTxAccessorContract files
DaniSomoza Jul 26, 2023
26692ea
fix relay-kit tests
DaniSomoza Jul 26, 2023
eb262f0
Add parseSafeTxGasErrorResponse util function
DaniSomoza Jul 26, 2023
b040b64
Add custom contracts
DaniSomoza Jul 26, 2023
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
6 changes: 3 additions & 3 deletions packages/account-abstraction-kit/src/AccountAbstraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ class AccountAbstraction {

const safeAddress = await this.#safeSdk.getAddress()

const standardizedSafeTx = await this.#relayPack.createRelayedTransaction(
this.#safeSdk,
const standardizedSafeTx = await this.#relayPack.createRelayedTransaction({
safe: this.#safeSdk,
transactions,
options
)
})

const safeSingletonContract = await getSafeContract({
ethAdapter: this.#ethAdapter,
Expand Down
213 changes: 211 additions & 2 deletions packages/protocol-kit/src/Safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ import {
SafeTransactionEIP712Args,
SafeVersion,
TransactionOptions,
TransactionResult
TransactionResult,
MetaTransactionData,
Transaction
} from '@safe-global/safe-core-sdk-types'
import {
PREDETERMINED_SALT_NONCE,
encodeSetupCallData,
predictSafeAddress
} from './contracts/utils'
import { DEFAULT_SAFE_VERSION } from './contracts/config'
import { predictSafeAddress } from './contracts/utils'
import ContractManager from './managers/contractManager'
import FallbackHandlerManager from './managers/fallbackHandlerManager'
import GuardManager from './managers/guardManager'
Expand Down Expand Up @@ -49,6 +55,11 @@ import {
standardizeSafeTransactionData
} from './utils/transactions/utils'
import { isSafeConfigWithPredictedSafe } from './utils/types'
import {
getMultiSendCallOnlyContract,
getProxyFactoryContract,
getSafeContract
} from './contracts/safeDeploymentContracts'

class Safe {
#predictedSafe?: PredictedSafeProps
Expand Down Expand Up @@ -1007,6 +1018,204 @@ class Safe {
)
return txResponse
}

/**
* Returns the Safe Transaction encoded
*
* @async
* @param {SafeTransaction} safeTransaction - The Safe transaction to be encoded.
* @returns {Promise<string>} The encoded transaction
*
*/
async getEncodedTransaction(safeTransaction: SafeTransaction): Promise<string> {
const safeVersion = await this.getContractVersion()
const chainId = await this.getChainId()
const customContracts = this.#contractManager.contractNetworks?.[chainId]
const isL1SafeMasterCopy = this.#contractManager.isL1SafeMasterCopy

const safeSingletonContract = await getSafeContract({
ethAdapter: this.#ethAdapter,
safeVersion: safeVersion,
isL1SafeMasterCopy,
customContracts
})

const encodedTransaction: string = safeSingletonContract.encode('execTransaction', [
safeTransaction.data.to,
safeTransaction.data.value,
safeTransaction.data.data,
safeTransaction.data.operation,
safeTransaction.data.safeTxGas,
safeTransaction.data.baseGas,
safeTransaction.data.gasPrice,
safeTransaction.data.gasToken,
safeTransaction.data.refundReceiver,
safeTransaction.encodedSignatures()
]) as string

return encodedTransaction
}

/**
* Wraps a Safe transaction into a Safe deployment batch.
*
* This function creates a transaction batch of 2 transactions, which includes the
* deployment of the Safe and the provided Safe transaction.
*
* @async
* @param {SafeTransaction} safeTransaction - The Safe transaction to be wrapped into the deployment batch.
* @param {TransactionOptions} [transactionOptions] - Optional. Options for the transaction, such as from, gas price, gas limit, etc.
* @param {string} [customSaltNonce] - Optional. a Custom salt nonce to be used for the deployment of the Safe. If not provided, a default value is used.
* @returns {Promise<Transaction>} A promise that resolves to a Transaction object representing the prepared batch of transactions.
* @throws Will throw an error if the safe is already deployed.
*
*/
async wrapSafeTransactionIntoDeploymentBatch(
safeTransaction: SafeTransaction,
transactionOptions?: TransactionOptions,
customSaltNonce?: string
): Promise<Transaction> {
const isSafeDeployed = await this.isSafeDeployed()

// if the safe is already deployed throws an error
if (isSafeDeployed) {
throw new Error('Safe already deployed')
}
yagopv marked this conversation as resolved.
Show resolved Hide resolved

// we create the deployment transaction
const safeDeploymentTransaction = await this.createSafeDeploymentTransaction(customSaltNonce)

// First transaction of the batch: The Safe deployment Transaction
const safeDeploymentBatchTransaction = {
to: safeDeploymentTransaction.to,
value: safeDeploymentTransaction.value,
data: safeDeploymentTransaction.data,
operation: OperationType.Call
}

// Second transaction of the batch: The Safe Transaction
const safeBatchTransaction = {
to: await this.getAddress(),
value: '0',
data: await this.getEncodedTransaction(safeTransaction),
operation: OperationType.Call
}

// transactions for the batch
const transactions = [safeDeploymentBatchTransaction, safeBatchTransaction]

// this is the transaction with the batch
const safeDeploymentBatch = await this.createTransactionBatch(transactions, transactionOptions)

return safeDeploymentBatch
}

/**
* Creates a Safe deployment transaction.
*
* This function prepares a transaction for the deployment of a Safe.
* Both the saltNonce and options parameters are optional, and if not
* provided, default values will be used.
*
* @async
* @param {string} [customSaltNonce] - Optional. a Custom salt nonce to be used for the deployment of the Safe. If not provided, a default value is used.
* @param {TransactionOptions} [options] - Optional. Options for the transaction, such as gas price, gas limit, etc.
* @returns {Promise<Transaction>} A promise that resolves to a Transaction object representing the prepared Safe deployment transaction.
*
*/
async createSafeDeploymentTransaction(
customSaltNonce?: string,
transactionOptions?: TransactionOptions
): Promise<Transaction> {
if (!this.#predictedSafe) {
throw new Error('Predict Safe should be present')
}

const { safeAccountConfig, safeDeploymentConfig } = this.#predictedSafe

const safeVersion = await this.getContractVersion()
const ethAdapter = this.#ethAdapter
const chainId = await ethAdapter.getChainId()
const isL1SafeMasterCopy = this.#contractManager.isL1SafeMasterCopy
const customContracts = this.#contractManager.contractNetworks?.[chainId]

const safeSingletonContract = await getSafeContract({
ethAdapter: this.#ethAdapter,
safeVersion,
isL1SafeMasterCopy,
customContracts
})

// we use the SafeProxyFactory.sol contract, see: https://github.com/safe-global/safe-contracts/blob/main/contracts/proxies/SafeProxyFactory.sol
const safeProxyFactoryContract = await getProxyFactoryContract({
ethAdapter,
safeVersion,
customContracts
})

// this is the call to the setup method that sets the threshold & owners of the new Safe, see: https://github.com/safe-global/safe-contracts/blob/main/contracts/Safe.sol#L95
const initializer = await encodeSetupCallData({
ethAdapter,
safeContract: safeSingletonContract,
safeAccountConfig: safeAccountConfig,
customContracts
})

const saltNonce = customSaltNonce || safeDeploymentConfig?.saltNonce || PREDETERMINED_SALT_NONCE

const safeDeployTransactionData = {
...transactionOptions, // optional transaction options like from, gasLimit, gasPrice...
to: safeProxyFactoryContract.getAddress(),
value: '0',
// we use the createProxyWithNonce method to create the Safe in a deterministic address, see: https://github.com/safe-global/safe-contracts/blob/main/contracts/proxies/SafeProxyFactory.sol#L52
data: safeProxyFactoryContract.encode('createProxyWithNonce', [
safeSingletonContract.getAddress(),
initializer, // call to the setup method to set the threshold & owners of the new Safe
saltNonce
])
}

return safeDeployTransactionData
}

/**
* This function creates a batch of the provided Safe transactions using the MultiSend contract.
* It groups the transactions together into a single transaction which can then be executed atomically.
*
* @async
* @function createTransactionBatch
* @param {MetaTransactionData[]} transactions - An array of MetaTransactionData objects to be batched together.
* @param {TransactionOption} [transactionOptions] - Optional TransactionOption object to specify additional options for the transaction batch.
* @returns {Promise<Transaction>} A Promise that resolves with the created transaction batch.
*
*/
async createTransactionBatch(
transactions: MetaTransactionData[],
transactionOptions?: TransactionOptions
): Promise<Transaction> {
const chainId = await this.#ethAdapter.getChainId()

// we use the MultiSend contract to create the batch, see: https://github.com/safe-global/safe-contracts/blob/main/contracts/libraries/MultiSendCallOnly.sol
const multiSendCallOnlyContract = await getMultiSendCallOnlyContract({
ethAdapter: this.#ethAdapter,
safeVersion: await this.getContractVersion(),
customContracts: this.#contractManager.contractNetworks?.[chainId]
})

// multiSend method with the transactions encoded
const batchData = multiSendCallOnlyContract.encode('multiSend', [
encodeMultiSendData(transactions) // encoded transactions
])

const transactionBatch = {
...transactionOptions, // optional transaction options like from, gasLimit, gasPrice...
to: multiSendCallOnlyContract.getAddress(),
value: '0',
data: batchData
}

return transactionBatch
}
}

export default Safe
12 changes: 11 additions & 1 deletion packages/protocol-kit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,22 @@ import {
StandardizeSafeTransactionDataProps,
SwapOwnerTxParams
} from './types'
import { EthSafeSignature } from './utils'
import {
EthSafeSignature,
estimateTxBaseGas,
estimateTxGas,
estimateSafeTxGas,
estimateSafeDeploymentGas
} from './utils'
import { SafeTransactionOptionalProps } from './utils/transactions/types'
import { encodeMultiSendData, standardizeSafeTransactionData } from './utils/transactions/utils'

export {
AddOwnerTxParams,
estimateTxBaseGas,
estimateTxGas,
estimateSafeTxGas,
estimateSafeDeploymentGas,
ConnectSafeConfig,
ConnectSafeConfigWithPredictedSafe,
ConnectSafeConfigWithSafeAddress,
Expand Down
34 changes: 22 additions & 12 deletions packages/protocol-kit/src/managers/contractManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ContractManager {
}

async init(config: SafeConfig): Promise<void> {
const { ethAdapter, isL1SafeMasterCopy, contractNetworks } = config
const { ethAdapter, isL1SafeMasterCopy, contractNetworks, predictedSafe, safeAddress } = config

const chainId = await ethAdapter.getChainId()
const customContracts = contractNetworks?.[chainId]
Expand All @@ -37,30 +37,40 @@ class ContractManager {
let safeVersion: SafeVersion

if (isSafeConfigWithPredictedSafe(config)) {
safeVersion = config.predictedSafe.safeDeploymentConfig?.safeVersion ?? DEFAULT_SAFE_VERSION
safeVersion = predictedSafe?.safeDeploymentConfig?.safeVersion ?? DEFAULT_SAFE_VERSION
} else {
const temporarySafeContract = await getSafeContract({
// We use the lastest version of the Safe contract to get the correct version of this Safe
const latestVersionOfTheSafeContract = await getSafeContract({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that this variable name may not be accurate, as the DEFAULT_SAFE_VERSION may not be the latest.

I would rename it to defaultSafeContractInstance

ethAdapter,
safeVersion: DEFAULT_SAFE_VERSION,
isL1SafeMasterCopy,
customSafeAddress: config.safeAddress,
customContracts
})
safeVersion = await temporarySafeContract.getVersion()
this.#safeContract = await getSafeContract({
ethAdapter,
safeVersion,
isL1SafeMasterCopy,
customSafeAddress: config.safeAddress,
customSafeAddress: safeAddress,
customContracts
})

// We get the correct version of the Safe from the blockchain
safeVersion = await latestVersionOfTheSafeContract.getVersion()

// We get the correct Safe Contract if the real Safe version is not the lastest
const isTheLastSafeVersion = safeVersion === DEFAULT_SAFE_VERSION

this.#safeContract = isTheLastSafeVersion
? latestVersionOfTheSafeContract
: await getSafeContract({
ethAdapter,
safeVersion,
isL1SafeMasterCopy,
customSafeAddress: safeAddress,
customContracts
})
}

this.#multiSendContract = await getMultiSendContract({
ethAdapter,
safeVersion,
customContracts
})

this.#multiSendCallOnlyContract = await getMultiSendCallOnlyContract({
ethAdapter,
safeVersion,
Expand Down
Loading
Loading