Skip to content

Commit f137fa4

Browse files
authored
Merge pull request #62 from AztecProtocol/pw/bridge-data-contruction
Refactored bridge data construction
2 parents 9bb23e8 + c7e85cb commit f137fa4

18 files changed

+4807
-9119
lines changed

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
"test:pinned": "forge test --fork-block-number 14000000 --match-contract 'Element|Set' -vvv --fork-url https://mainnet.infura.io/v3/9928b52099854248b3a096be07a6b23c",
1414
"test:contracts:block": "forge test --fork-block-number",
1515
"cast": "cast",
16-
"test:contracts": "forge test --no-match-contract 'Element|Set' -vvv && yarn test:pinned",
16+
"test:contracts": "forge test --no-match-contract 'Element|Set' --no-match-test 'testRedeemFlow|testAsyncLimitOrder|testTwoPartMint' -vvv && yarn test:pinned",
1717
"build": "yarn clean && yarn compile:typechain && yarn compile:client-dest"
1818
},
1919
"dependencies": {
20-
"axios": "^0.26.1"
20+
"@ethersproject/providers": "^5.6.5",
21+
"axios": "^0.26.1",
22+
"detect-node": "^2.1.0",
23+
"sha3": "^2.1.4"
2124
},
2225
"jest": {
2326
"transform": {
@@ -39,6 +42,7 @@
3942
"@typechain/ethers-v4": "^7.0.0",
4043
"@typechain/ethers-v5": "^9.0.0",
4144
"@typechain/hardhat": "^4.0.0",
45+
"@types/detect-node": "^2.0.0",
4246
"@types/jest": "^27.4.0",
4347
"ethers": "^5.5.4",
4448
"hardhat": "^2.6.8",
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { Keccak } from 'sha3';
2+
import { randomBytes } from '../random';
3+
4+
const hash = new Keccak(256);
5+
6+
/**
7+
* Takes a string hex input e.g. `deadbeef` and returns the same.
8+
*/
9+
function sha3(input: string) {
10+
hash.reset();
11+
hash.update(input);
12+
return hash.digest('hex');
13+
}
14+
15+
export class EthAddress {
16+
public static ZERO = new EthAddress(Buffer.alloc(20));
17+
18+
constructor(private buffer: Buffer) {
19+
if (buffer.length === 32) {
20+
if (!buffer.slice(0, 12).equals(Buffer.alloc(12))) {
21+
throw new Error('Invalid address buffer.');
22+
} else {
23+
this.buffer = buffer.slice(12);
24+
}
25+
} else if (buffer.length !== 20) {
26+
throw new Error('Invalid address buffer.');
27+
}
28+
}
29+
30+
public static fromString(address: string) {
31+
if (!EthAddress.isAddress(address)) {
32+
throw new Error(`Invalid address string: ${address}`);
33+
}
34+
return new EthAddress(Buffer.from(address.replace(/^0x/i, ''), 'hex'));
35+
}
36+
37+
public static randomAddress() {
38+
return new EthAddress(randomBytes(20));
39+
}
40+
41+
public static isAddress(address: string) {
42+
if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
43+
// Does not have the basic requirements of an address.
44+
return false;
45+
} else if (/^(0x|0X)?[0-9a-f]{40}$/.test(address) || /^(0x|0X)?[0-9A-F]{40}$/.test(address)) {
46+
// It's ALL lowercase or ALL upppercase.
47+
return true;
48+
} else {
49+
return EthAddress.checkAddressChecksum(address);
50+
}
51+
}
52+
53+
public isZero() {
54+
return this.equals(EthAddress.ZERO);
55+
}
56+
57+
public static checkAddressChecksum(address: string) {
58+
address = address.replace(/^0x/i, '');
59+
const addressHash = sha3(address.toLowerCase());
60+
61+
for (let i = 0; i < 40; i++) {
62+
// The nth letter should be uppercase if the nth digit of casemap is 1.
63+
if (
64+
(parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) ||
65+
(parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])
66+
) {
67+
return false;
68+
}
69+
}
70+
return true;
71+
}
72+
73+
public static toChecksumAddress(address: string) {
74+
if (!EthAddress.isAddress(address)) {
75+
throw new Error('Invalid address string.');
76+
}
77+
78+
address = address.toLowerCase().replace(/^0x/i, '');
79+
const addressHash = sha3(address);
80+
let checksumAddress = '0x';
81+
82+
for (let i = 0; i < address.length; i++) {
83+
// If ith character is 9 to f then make it uppercase.
84+
if (parseInt(addressHash[i], 16) > 7) {
85+
checksumAddress += address[i].toUpperCase();
86+
} else {
87+
checksumAddress += address[i];
88+
}
89+
}
90+
return checksumAddress;
91+
}
92+
93+
public equals(rhs: EthAddress) {
94+
return this.buffer.equals(rhs.toBuffer());
95+
}
96+
97+
public toString() {
98+
return EthAddress.toChecksumAddress(this.buffer.toString('hex'));
99+
}
100+
101+
public toBuffer() {
102+
return this.buffer;
103+
}
104+
105+
public toBuffer32() {
106+
const buffer = Buffer.alloc(32);
107+
this.buffer.copy(buffer, 12);
108+
return buffer;
109+
}
110+
}

src/client/aztec/eth_address/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './eth_address';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export declare enum ProviderError {
2+
USER_REJECTED = 4001,
3+
UNAUTHORIZED = 4100,
4+
UNSUPPORTED = 4200,
5+
DISCONNECTED = 4900,
6+
CHAIN_DISCONNECTED = 4901,
7+
}
8+
export interface ProviderMessage {
9+
readonly type: string;
10+
readonly data: unknown;
11+
}
12+
export interface RequestArguments {
13+
readonly method: string;
14+
readonly params?: any[];
15+
}
16+
export interface ProviderRpcError extends Error {
17+
message: string;
18+
code: ProviderError | number;
19+
data?: unknown;
20+
}
21+
export interface ProviderConnectInfo {
22+
readonly chainId: string;
23+
}
24+
export declare type EthereumProviderNotifications =
25+
| 'connect'
26+
| 'disconnect'
27+
| 'chainChanged'
28+
| 'accountsChanged'
29+
| 'message';
30+
/**
31+
* Interface defining an EIP1193 compatible provider. This is the standard that all future providers should adhere to.
32+
* The Aztec SDK accepts such a provider. If non standard providers wish to be used, wrap them in an adapter first.
33+
* Two adapters are provided, an EthersAdapter for ethers providers, and Web3Adapter, for legacy web3 providers.
34+
*/
35+
export interface EthereumProvider {
36+
request(args: RequestArguments): Promise<any>;
37+
on(notification: 'connect', listener: (connectInfo: ProviderConnectInfo) => void): this;
38+
on(notification: 'disconnect', listener: (error: ProviderRpcError) => void): this;
39+
on(notification: 'chainChanged', listener: (chainId: string) => void): this;
40+
on(notification: 'accountsChanged', listener: (accounts: string[]) => void): this;
41+
on(notification: 'message', listener: (message: ProviderMessage) => void): this;
42+
removeListener(notification: 'connect', listener: (connectInfo: ProviderConnectInfo) => void): this;
43+
removeListener(notification: 'disconnect', listener: (error: ProviderRpcError) => void): this;
44+
removeListener(notification: 'chainChanged', listener: (chainId: string) => void): this;
45+
removeListener(notification: 'accountsChanged', listener: (accounts: string[]) => void): this;
46+
removeListener(notification: 'message', listener: (message: ProviderMessage) => void): this;
47+
}
48+
//# sourceMappingURL=ethereum_provider.d.ts.map

src/client/aztec/provider/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './ethereum_provider';
2+
export * from './web3_provider';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { Web3Provider } from '@ethersproject/providers';
2+
import { EthereumProvider } from './ethereum_provider';
3+
4+
export const createWeb3Provider = (ethereumProvider: EthereumProvider) => new Web3Provider(ethereumProvider);

src/client/aztec/random/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import isNode from 'detect-node';
2+
3+
const getWebCrypto = () => {
4+
if (typeof window !== 'undefined' && window.crypto) return window.crypto;
5+
if (typeof self !== 'undefined' && self.crypto) return self.crypto;
6+
return undefined;
7+
};
8+
9+
export const randomBytes = (len: number) => {
10+
if (isNode) {
11+
// eslint-disable-next-line @typescript-eslint/no-var-requires
12+
return require('crypto').randomBytes(len) as Buffer;
13+
}
14+
15+
const crypto = getWebCrypto();
16+
if (crypto) {
17+
const buf = Buffer.alloc(len);
18+
crypto.getRandomValues(buf);
19+
return buf;
20+
}
21+
22+
throw new Error('randomBytes UnsupportedEnvironment');
23+
};

src/client/element/element-bridge-data.test.ts

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
import { ElementBridgeData } from './element-bridge-data';
22
import { BigNumber } from 'ethers';
33
import { randomBytes } from 'crypto';
4-
import { RollupProcessor, ElementBridge, IVault } from '../../../typechain-types';
5-
import { AsyncDefiBridgeProcessedEventFilter } from '../../../typechain-types/RollupProcessor';
4+
import {
5+
RollupProcessor,
6+
ElementBridge,
7+
IVault,
8+
ElementBridge__factory,
9+
RollupProcessor__factory,
10+
IVault__factory,
11+
} from '../../../typechain-types';
612
import { BridgeId, BitConfig } from '../aztec/bridge_id';
713
import { AztecAssetType } from '../bridge-data';
814
import { AddressZero } from '@ethersproject/constants';
15+
import { EthAddress } from '../aztec/eth_address';
16+
17+
jest.mock('../aztec/provider', () => ({
18+
createWeb3Provider: jest.fn(),
19+
}));
920

1021
type Mockify<T> = {
1122
[P in keyof T]: jest.Mock;
@@ -116,12 +127,20 @@ describe('element bridge data', () => {
116127
} as any,
117128
} as any;
118129

130+
const createElementBridgeData = (
131+
element: ElementBridge = elementBridge as any,
132+
balancer: IVault = balancerContract as any,
133+
rollup: RollupProcessor = rollupContract as any,
134+
chainProperties: { chunkSize: number } = { chunkSize: 10 },
135+
) => {
136+
ElementBridge__factory.connect = () => element as any;
137+
IVault__factory.connect = () => balancer as any;
138+
RollupProcessor__factory.connect = () => rollup as any;
139+
return ElementBridgeData.create({} as any, EthAddress.ZERO, EthAddress.ZERO, EthAddress.ZERO, chainProperties); // can pass in dummy values here as the above factories do all of the work
140+
};
141+
119142
it('should return the correct amount of interest', async () => {
120-
const elementBridgeData = new ElementBridgeData(
121-
elementBridge as any,
122-
balancerContract as any,
123-
rollupContract as any,
124-
);
143+
const elementBridgeData = createElementBridgeData();
125144
interactions[56] = {
126145
quantityPT: BigNumber.from(outputValue),
127146
expiry: BigNumber.from(expiration1),
@@ -141,13 +160,7 @@ describe('element bridge data', () => {
141160
});
142161

143162
it('should return the correct amount of interest for multiple interactions', async () => {
144-
const elementBridgeData = new ElementBridgeData(
145-
elementBridge as any,
146-
balancerContract as any,
147-
rollupContract as any,
148-
{ chunkSize: 10 },
149-
);
150-
163+
const elementBridgeData = createElementBridgeData();
151164
const testInteraction = async (nonce: number) => {
152165
const defiEvent = getDefiEvent(nonce)!;
153166
const bridgeId = BridgeId.fromBigInt(defiEvent.bridgeId);
@@ -179,11 +192,7 @@ describe('element bridge data', () => {
179192
});
180193

181194
it('requesting the present value of an unknown interaction should return empty values', async () => {
182-
const elementBridgeData = new ElementBridgeData(
183-
elementBridge as any,
184-
balancerContract as any,
185-
rollupContract as any,
186-
);
195+
const elementBridgeData = createElementBridgeData();
187196
const values = await elementBridgeData.getInteractionPresentValue(57n);
188197
expect(values).toStrictEqual([]);
189198
});
@@ -206,11 +215,7 @@ describe('element bridge data', () => {
206215
},
207216
} as any;
208217

209-
const elementBridgeData = new ElementBridgeData(
210-
elementBridge as any,
211-
balancerContract as any,
212-
rollupContract as any,
213-
);
218+
const elementBridgeData = createElementBridgeData(elementBridge as any);
214219
const expiration = await elementBridgeData.getExpiration(1n);
215220

216221
expect(expiration).toBe(BigInt(endDate));
@@ -241,7 +246,7 @@ describe('element bridge data', () => {
241246
}),
242247
};
243248

244-
const elementBridgeData = new ElementBridgeData(
249+
const elementBridgeData = createElementBridgeData(
245250
elementBridge as any,
246251
balancerContract as any,
247252
rollupContract as any,
@@ -297,7 +302,7 @@ describe('element bridge data', () => {
297302
getPoolTokens: jest.fn().mockResolvedValue([[tokenAddress], [BigNumber.from(BigInt(tokenBalance))]]),
298303
};
299304

300-
const elementBridgeData = new ElementBridgeData(
305+
const elementBridgeData = createElementBridgeData(
301306
elementBridge as any,
302307
balancerContract as any,
303308
rollupContract as any,

src/client/element/element-bridge-data.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { BridgeId } from '../aztec/bridge_id';
22
import { AddressZero } from '@ethersproject/constants';
33
import { AssetValue, AsyncYieldBridgeData, AuxDataConfig, AztecAsset, SolidityType } from '../bridge-data';
4-
import { ElementBridge, RollupProcessor, IVault } from '../../../typechain-types';
4+
import {
5+
ElementBridge,
6+
IVault,
7+
RollupProcessor,
8+
ElementBridge__factory,
9+
IVault__factory,
10+
RollupProcessor__factory,
11+
} from '../../../typechain-types';
512
import { AsyncDefiBridgeProcessedEvent } from '../../../typechain-types/RollupProcessor';
13+
import { createWeb3Provider, EthereumProvider } from '../aztec/provider/';
14+
import { EthAddress } from '../aztec/eth_address';
615

716
export type BatchSwapStep = {
817
poolId: string;
@@ -60,21 +69,28 @@ const decodeEvent = async (event: AsyncDefiBridgeProcessedEvent) => {
6069
};
6170

6271
export class ElementBridgeData implements AsyncYieldBridgeData {
63-
private elementBridgeContract: ElementBridge;
64-
private rollupContract: RollupProcessor;
65-
private balancerContract: IVault;
6672
public scalingFactor = BigInt(1n * 10n ** 18n);
6773
private interactionBlockNumbers: Array<EventBlock> = [];
6874

69-
constructor(
70-
elementBridge: ElementBridge,
71-
balancer: IVault,
72-
rollupContract: RollupProcessor,
73-
private chainProperties: ChainProperties = { chunkSize: 10000 },
75+
private constructor(
76+
private elementBridgeContract: ElementBridge,
77+
private balancerContract: IVault,
78+
private rollupContract: RollupProcessor,
79+
private chainProperties: ChainProperties,
80+
) {}
81+
82+
static create(
83+
provider: EthereumProvider,
84+
elementBridgeAddress: EthAddress,
85+
balancerAddress: EthAddress,
86+
rollupContractAddress: EthAddress,
87+
chainProperties: ChainProperties = { chunkSize: 10000 },
7488
) {
75-
this.elementBridgeContract = elementBridge;
76-
this.rollupContract = rollupContract;
77-
this.balancerContract = balancer;
89+
const ethersProvider = createWeb3Provider(provider);
90+
const elementBridgeContract = ElementBridge__factory.connect(elementBridgeAddress.toString(), ethersProvider);
91+
const rollupContract = RollupProcessor__factory.connect(rollupContractAddress.toString(), ethersProvider);
92+
const vaultContract = IVault__factory.connect(balancerAddress.toString(), ethersProvider);
93+
return new ElementBridgeData(elementBridgeContract, vaultContract, rollupContract, chainProperties);
7894
}
7995

8096
private async storeEventBlocks(events: AsyncDefiBridgeProcessedEvent[]) {

0 commit comments

Comments
 (0)