Skip to content

Commit

Permalink
feat(abstract-eth): add v4 forwarder creation logic
Browse files Browse the repository at this point in the history
WIN-1926

This PR updates address initialization logic in transaction builder to support new v4
forwarders
TICKET: WIN-1926
  • Loading branch information
gianchandania committed Feb 7, 2024
1 parent 62bd8c8 commit 2e1a7ce
Show file tree
Hide file tree
Showing 11 changed files with 558 additions and 21 deletions.
1 change: 1 addition & 0 deletions modules/abstract-eth/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,5 @@ export interface WalletInitializationData {
export interface ForwarderInitializationData {
baseAddress?: string;
addressCreationSalt?: string;
feeAddress?: string;
}
55 changes: 41 additions & 14 deletions modules/abstract-eth/src/lib/transactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ import {
decodeWalletCreationData,
flushCoinsData,
flushTokensData,
getAddressInitializationData,
getV1AddressInitializationData,
getAddressInitDataAllForwarderVersions,
getCommon,
getProxyInitcode,
hasSignature,
isValidEthAddress,
getV1WalletInitializationData,
getCreateForwarderParamsAndTypes,
} from './utils';
import { defaultWalletVersion, defaultForwarderVersion, walletSimpleConstructor } from './walletUtil';
import { defaultWalletVersion, walletSimpleConstructor } from './walletUtil';
import { ERC1155TransferBuilder } from './transferBuilders/transferBuilderERC1155';
import { ERC721TransferBuilder } from './transferBuilders/transferBuilderERC721';
import { Transaction } from './transaction';
Expand Down Expand Up @@ -77,6 +77,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
private _forwarderVersion: number;
private _initCode: string;
private _baseAddress: string;
private _feeAddress: string;

// generic contract call builder
// encoded contract call hex
Expand Down Expand Up @@ -116,8 +117,8 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {

this.transaction.setTransactionType(this._type);
transactionData.from = this._sourceKeyPair ? this._sourceKeyPair.getAddress() : undefined;
this.transaction.setTransactionData(transactionData);

this.transaction.setTransactionData(transactionData);
// Build and sign a new transaction based on the latest changes
if (this._sourceKeyPair && this._sourceKeyPair.getKeys().prv) {
await this.transaction.sign(this._sourceKeyPair);
Expand Down Expand Up @@ -230,11 +231,16 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
break;
case TransactionType.AddressInitialization:
this.setContract(transactionJson.to);
const { baseAddress, addressCreationSalt } = decodeForwarderCreationData(transactionJson.data);
const { baseAddress, addressCreationSalt, feeAddress } = decodeForwarderCreationData(transactionJson.data);
if (baseAddress && addressCreationSalt) {
this.forwarderVersion(1);
this.baseAddress(baseAddress);
this.salt(addressCreationSalt);
if (feeAddress) {
this.feeAddress(feeAddress);
this.forwarderVersion(4);
} else {
this.forwarderVersion(1);
}
const forwarderImplementationAddress = (this._coinConfig.network as EthereumNetwork)
.forwarderImplementationAddress as string;
if (forwarderImplementationAddress) {
Expand Down Expand Up @@ -651,10 +657,12 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
* @returns {TxData} The Ethereum transaction data
*/
private buildAddressInitializationTransaction(): TxData {
const addressInitData =
this._forwarderVersion === defaultForwarderVersion
? getAddressInitializationData()
: getV1AddressInitializationData(this._baseAddress, this._salt);
const addressInitData = getAddressInitDataAllForwarderVersions(
this._forwarderVersion,
this._baseAddress,
this._salt,
this._feeAddress
);
const tx: TxData = this.buildBase(addressInitData);
tx.to = this._contractAddress;

Expand All @@ -664,9 +672,16 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {

if (this._salt && this._initCode) {
const saltBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(this._salt), 32);
// Hash the wallet base address with the given salt, so the address directly relies on the base address

const { createForwarderParams, createForwarderTypes } = getCreateForwarderParamsAndTypes(
this._baseAddress,
saltBuffer,
this._feeAddress
);

// Hash the wallet base address and fee address if present with the given salt, so the address directly relies on the base address and fee address
const calculationSalt = ethUtil.bufferToHex(
EthereumAbi.soliditySHA3(['address', 'bytes32'], [this._baseAddress, saltBuffer])
EthereumAbi.soliditySHA3(createForwarderTypes, createForwarderParams)
);
tx.deployedAddress = calculateForwarderV1Address(this._contractAddress, calculationSalt, this._initCode);
}
Expand Down Expand Up @@ -757,7 +772,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
* @param {number} version forwarder version
*/
forwarderVersion(version: number): void {
if (version < 0 || version > 2) {
if (version < 0 || version > 4 || version === 3) {
throw new BuildTransactionError(`Invalid forwarder version: ${version}`);
}

Expand Down Expand Up @@ -791,7 +806,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
* @param {number} version wallet version
*/
walletVersion(version: number): void {
if (version < 0 || version > 3) {
if (version < 0 || version > 4 || version === 3) {
throw new BuildTransactionError(`Invalid wallet version: ${version}`);
}

Expand All @@ -809,4 +824,16 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
}
this._baseAddress = address;
}

/**
* Set the fee address of the wallet
*
* @param {string} address The fee address of the wallet
*/
feeAddress(address: string): void {
if (!isValidEthAddress(address)) {
throw new BuildTransactionError('Invalid address: ' + address);
}
this._feeAddress = address;
}
}
91 changes: 84 additions & 7 deletions modules/abstract-eth/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ import {
v1CreateWalletMethodId,
createV1ForwarderTypes,
recoveryWalletInitializationFirstBytes,
defaultForwarderVersion,
createV4ForwarderTypes,
v4CreateForwarderMethodId,
} from './walletUtil';
import { EthTransactionData } from './types';

Expand Down Expand Up @@ -477,6 +480,7 @@ const transactionTypesMap = {
[v1CreateWalletMethodId]: TransactionType.WalletInitialization,
[createForwarderMethodId]: TransactionType.AddressInitialization,
[v1CreateForwarderMethodId]: TransactionType.AddressInitialization,
[v4CreateForwarderMethodId]: TransactionType.AddressInitialization,
[sendMultisigMethodId]: TransactionType.Send,
[flushForwarderTokensMethodId]: TransactionType.FlushTokens,
[flushCoinsMethodId]: TransactionType.FlushCoins,
Expand Down Expand Up @@ -672,37 +676,98 @@ export function getV1WalletInitializationData(walletOwners: string[], salt: stri
}

/**
* Returns the create address method calling data for v1 wallets
* Returns the create address method calling data for v1, v2, v4 forwarders
*
* @param {string} baseAddress - The address of the wallet contract
* @param {string} salt - The salt for address initialization transactions
* @param {string} feeAddress - The fee address for the enterprise
* @returns {string} - the createForwarder method encoded
*/
export function getV1AddressInitializationData(baseAddress: string, salt: string): string {
export function getV1AddressInitializationData(baseAddress: string, salt: string, feeAddress?: string): string {
const saltBuffer = setLengthLeft(toBuffer(salt), 32);
const params = [baseAddress, saltBuffer];
const method = EthereumAbi.methodID('createForwarder', createV1ForwarderTypes);
const args = EthereumAbi.rawEncode(createV1ForwarderTypes, params);
const { createForwarderParams, createForwarderTypes } = getCreateForwarderParamsAndTypes(
baseAddress,
saltBuffer,
feeAddress
);

const method = EthereumAbi.methodID('createForwarder', createForwarderTypes);
const args = EthereumAbi.rawEncode(createForwarderTypes, createForwarderParams);
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}

/**
* Returns the create address method calling data for all forwarder versions
*
* @param {number} forwarderVersion - The version of the forwarder to create
* @param {string} baseAddress - The address of the wallet contract
* @param {string} salt - The salt for address initialization transactions
* @param {string} feeAddress - The fee address for the enterprise
* @returns {string} - the createForwarder method encoded
*
*/
export function getAddressInitDataAllForwarderVersions(
forwarderVersion: number,
baseAddress: string,
salt: string,
feeAddress?: string
): string {
if (forwarderVersion === defaultForwarderVersion) {
return getAddressInitializationData();
} else {
return getV1AddressInitializationData(baseAddress, salt, feeAddress);
}
}

/**
* Returns the createForwarderTypes and createForwarderParams for all forwarder versions
*
* @param {string} baseAddress - The address of the wallet contract
* @param {Buffer} saltBuffer - The salt for address initialization transaction
* @param {string} feeAddress - The fee address for the enterprise
* @returns {createForwarderParams: (string | Buffer)[], createForwarderTypes: string[]}
*/
export function getCreateForwarderParamsAndTypes(
baseAddress: string,
saltBuffer: Buffer,
feeAddress?: string
): { createForwarderParams: (string | Buffer)[]; createForwarderTypes: string[] } {
let createForwarderParams;
let createForwarderTypes;
if (feeAddress) {
createForwarderParams = [baseAddress, feeAddress, saltBuffer];
createForwarderTypes = createV4ForwarderTypes;
} else {
createForwarderParams = [baseAddress, saltBuffer];
createForwarderTypes = createV1ForwarderTypes;
}
return { createForwarderParams, createForwarderTypes };
}

/**
* Decode the given ABI-encoded create forwarder data and return parsed fields
*
* @param data The data to decode
* @returns parsed transfer data
*/
export function decodeForwarderCreationData(data: string): ForwarderInitializationData {
if (!(data.startsWith(v1CreateForwarderMethodId) || data.startsWith(createForwarderMethodId))) {
if (
!(
data.startsWith(v4CreateForwarderMethodId) ||
data.startsWith(v1CreateForwarderMethodId) ||
data.startsWith(createForwarderMethodId)
)
) {
throw new BuildTransactionError(`Invalid address bytecode: ${data}`);
}

if (data.startsWith(createForwarderMethodId)) {
return {
baseAddress: undefined,
addressCreationSalt: undefined,
feeAddress: undefined,
};
} else {
} else if (data.startsWith(v1CreateForwarderMethodId)) {
const [baseAddress, saltBuffer] = getRawDecoded(
createV1ForwarderTypes,
getBufferedByteCode(v1CreateForwarderMethodId, data)
Expand All @@ -711,6 +776,18 @@ export function decodeForwarderCreationData(data: string): ForwarderInitializati
return {
baseAddress: addHexPrefix(baseAddress as string),
addressCreationSalt: bufferToHex(saltBuffer as Buffer),
feeAddress: undefined,
};
} else {
const [baseAddress, feeAddress, saltBuffer] = getRawDecoded(
createV4ForwarderTypes,
getBufferedByteCode(v4CreateForwarderMethodId, data)
);

return {
baseAddress: addHexPrefix(baseAddress as string),
feeAddress: addHexPrefix(feeAddress as string),
addressCreationSalt: bufferToHex(saltBuffer as Buffer),
};
}
}
2 changes: 2 additions & 0 deletions modules/abstract-eth/src/lib/walletUtil.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const sendMultisigMethodId = '0x39125215';
export const sendMultisigTokenMethodId = '0x0dcd7a6c';
export const v1CreateForwarderMethodId = '0xfb90b320';
export const v4CreateForwarderMethodId = '0x13b2f75c';
export const v1WalletInitializationFirstBytes = '0x60806040';
export const v1CreateWalletMethodId = '0x7117f3fa';
export const createForwarderMethodId = '0xa68a76cc';
Expand Down Expand Up @@ -29,3 +30,4 @@ export const ERC721SafeTransferTypes = ['address', 'address', 'uint256', 'bytes'
export const ERC1155SafeTransferTypes = ['address', 'address', 'uint256', 'uint256', 'bytes'];
export const ERC1155BatchTransferTypes = ['address', 'address', 'uint256[]', 'uint256[]', 'bytes'];
export const createV1ForwarderTypes = ['address', 'bytes32'];
export const createV4ForwarderTypes = ['address', 'address', 'bytes32'];
Loading

0 comments on commit 2e1a7ce

Please sign in to comment.