@hyperlane-xyz/sdk 42.0.0 → 44.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/consts/multisigIsm.d.ts.map +1 -1
- package/dist/consts/multisigIsm.js +12 -0
- package/dist/consts/multisigIsm.js.map +1 -1
- package/dist/core/adapters/StarknetCoreAdapter.d.ts.map +1 -1
- package/dist/core/adapters/StarknetCoreAdapter.js +3 -3
- package/dist/core/adapters/StarknetCoreAdapter.js.map +1 -1
- package/dist/core/types.d.ts +47 -1635
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +2 -2
- package/dist/core/types.js.map +1 -1
- package/dist/deploy/HyperlaneDeployer.js +1 -1
- package/dist/deploy/proxy.d.ts +8 -40
- package/dist/deploy/proxy.d.ts.map +1 -1
- package/dist/deploy/proxy.js +4 -4
- package/dist/deploy/proxy.js.map +1 -1
- package/dist/deploy/types.d.ts +1 -19
- package/dist/deploy/types.d.ts.map +1 -1
- package/dist/fee/EvmTokenFeeDeployer.d.ts.map +1 -1
- package/dist/fee/EvmTokenFeeDeployer.hardhat-test.js +68 -0
- package/dist/fee/EvmTokenFeeDeployer.hardhat-test.js.map +1 -1
- package/dist/fee/EvmTokenFeeDeployer.js +18 -10
- package/dist/fee/EvmTokenFeeDeployer.js.map +1 -1
- package/dist/fee/EvmTokenFeeModule.d.ts.map +1 -1
- package/dist/fee/EvmTokenFeeModule.hardhat-test.js +18 -8
- package/dist/fee/EvmTokenFeeModule.hardhat-test.js.map +1 -1
- package/dist/fee/EvmTokenFeeModule.js +8 -1
- package/dist/fee/EvmTokenFeeModule.js.map +1 -1
- package/dist/fee/EvmTokenFeeReader.d.ts +2 -2
- package/dist/fee/EvmTokenFeeReader.d.ts.map +1 -1
- package/dist/fee/EvmTokenFeeReader.js.map +1 -1
- package/dist/fee/feeConfigMapping.d.ts.map +1 -1
- package/dist/fee/feeConfigMapping.js +1 -0
- package/dist/fee/feeConfigMapping.js.map +1 -1
- package/dist/fee/types.d.ts +147 -646
- package/dist/fee/types.d.ts.map +1 -1
- package/dist/fee/types.js +26 -17
- package/dist/fee/types.js.map +1 -1
- package/dist/fork/types.d.ts +125 -1924
- package/dist/fork/types.d.ts.map +1 -1
- package/dist/fork/types.js +3 -3
- package/dist/fork/types.js.map +1 -1
- package/dist/gas/oracle/types.d.ts +5 -56
- package/dist/gas/oracle/types.d.ts.map +1 -1
- package/dist/hook/EvmHookModule.d.ts.map +1 -1
- package/dist/hook/EvmHookModule.hardhat-test.js +12 -1
- package/dist/hook/EvmHookModule.hardhat-test.js.map +1 -1
- package/dist/hook/EvmHookModule.js +3 -0
- package/dist/hook/EvmHookModule.js.map +1 -1
- package/dist/hook/HyperlaneHookDeployer.d.ts +12 -0
- package/dist/hook/HyperlaneHookDeployer.d.ts.map +1 -1
- package/dist/hook/HyperlaneHookDeployer.hardhat-test.d.ts +2 -0
- package/dist/hook/HyperlaneHookDeployer.hardhat-test.d.ts.map +1 -0
- package/dist/hook/HyperlaneHookDeployer.hardhat-test.js +322 -0
- package/dist/hook/HyperlaneHookDeployer.hardhat-test.js.map +1 -0
- package/dist/hook/HyperlaneHookDeployer.js +262 -5
- package/dist/hook/HyperlaneHookDeployer.js.map +1 -1
- package/dist/hook/types.d.ts +44 -319
- package/dist/hook/types.d.ts.map +1 -1
- package/dist/hook/types.js +18 -11
- package/dist/hook/types.js.map +1 -1
- package/dist/ica/types.d.ts +35 -900
- package/dist/ica/types.d.ts.map +1 -1
- package/dist/ica/types.js +1 -1
- package/dist/ica/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ism/EvmIsmModule.d.ts +1 -1
- package/dist/ism/EvmIsmModule.d.ts.map +1 -1
- package/dist/ism/EvmIsmModule.hardhat-test.js +35 -1
- package/dist/ism/EvmIsmModule.hardhat-test.js.map +1 -1
- package/dist/ism/EvmIsmModule.js +15 -11
- package/dist/ism/EvmIsmModule.js.map +1 -1
- package/dist/ism/HyperlaneIsmFactory.d.ts +3 -1
- package/dist/ism/HyperlaneIsmFactory.d.ts.map +1 -1
- package/dist/ism/HyperlaneIsmFactory.hardhat-test.js +61 -3
- package/dist/ism/HyperlaneIsmFactory.hardhat-test.js.map +1 -1
- package/dist/ism/HyperlaneIsmFactory.js +42 -6
- package/dist/ism/HyperlaneIsmFactory.js.map +1 -1
- package/dist/ism/types.d.ts +48 -330
- package/dist/ism/types.d.ts.map +1 -1
- package/dist/ism/types.js +8 -10
- package/dist/ism/types.js.map +1 -1
- package/dist/metadata/agentConfig.d.ts +537 -10384
- package/dist/metadata/agentConfig.d.ts.map +1 -1
- package/dist/metadata/agentConfig.js +18 -18
- package/dist/metadata/agentConfig.js.map +1 -1
- package/dist/metadata/chainMetadataConversion.d.ts.map +1 -1
- package/dist/metadata/chainMetadataConversion.js +1 -0
- package/dist/metadata/chainMetadataConversion.js.map +1 -1
- package/dist/metadata/chainMetadataTypes.d.ts +70 -4678
- package/dist/metadata/chainMetadataTypes.d.ts.map +1 -1
- package/dist/metadata/chainMetadataTypes.js +8 -7
- package/dist/metadata/chainMetadataTypes.js.map +1 -1
- package/dist/metadata/customZodTypes.d.ts +5 -5
- package/dist/metadata/customZodTypes.d.ts.map +1 -1
- package/dist/metadata/customZodTypes.js +4 -5
- package/dist/metadata/customZodTypes.js.map +1 -1
- package/dist/metadata/deploymentArtifacts.d.ts +1 -13
- package/dist/metadata/deploymentArtifacts.d.ts.map +1 -1
- package/dist/metadata/matchingList.d.ts +12 -40
- package/dist/metadata/matchingList.d.ts.map +1 -1
- package/dist/metadata/warpRouteConfig.d.ts +8 -114
- package/dist/metadata/warpRouteConfig.d.ts.map +1 -1
- package/dist/metadata/warpRouteConfig.js +3 -3
- package/dist/metadata/warpRouteConfig.js.map +1 -1
- package/dist/middleware/account/InterchainAccount.d.ts.map +1 -1
- package/dist/middleware/account/InterchainAccount.js +12 -8
- package/dist/middleware/account/InterchainAccount.js.map +1 -1
- package/dist/middleware/account/InterchainAccount.test.js +22 -3
- package/dist/middleware/account/InterchainAccount.test.js.map +1 -1
- package/dist/middleware/account/icaCalls.d.ts +13 -145
- package/dist/middleware/account/icaCalls.d.ts.map +1 -1
- package/dist/middleware/account/types.d.ts +11 -105
- package/dist/middleware/account/types.d.ts.map +1 -1
- package/dist/predicate/PredicateApiClient.d.ts +1 -11
- package/dist/predicate/PredicateApiClient.d.ts.map +1 -1
- package/dist/providers/MultiProvider.d.ts.map +1 -1
- package/dist/providers/MultiProvider.js +3 -2
- package/dist/providers/MultiProvider.js.map +1 -1
- package/dist/providers/ProviderType.d.ts +4 -4
- package/dist/providers/ProviderType.d.ts.map +1 -1
- package/dist/providers/transactions/submitter/builder/types.d.ts +13 -280
- package/dist/providers/transactions/submitter/builder/types.d.ts.map +1 -1
- package/dist/providers/transactions/submitter/builder/types.js +2 -4
- package/dist/providers/transactions/submitter/builder/types.js.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.d.ts +2 -0
- package/dist/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.d.ts.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.js +15 -7
- package/dist/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.js.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.test.d.ts +2 -0
- package/dist/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.test.d.ts.map +1 -0
- package/dist/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.test.js +145 -0
- package/dist/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.test.js.map +1 -0
- package/dist/providers/transactions/submitter/ethersV5/types.d.ts +30 -102
- package/dist/providers/transactions/submitter/ethersV5/types.d.ts.map +1 -1
- package/dist/providers/transactions/submitter/ethersV5/types.js +3 -21
- package/dist/providers/transactions/submitter/ethersV5/types.js.map +1 -1
- package/dist/providers/transactions/submitter/types.d.ts +5 -51
- package/dist/providers/transactions/submitter/types.d.ts.map +1 -1
- package/dist/providers/transactions/types.d.ts +1 -9
- package/dist/providers/transactions/types.d.ts.map +1 -1
- package/dist/router/types.d.ts +33 -819
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/types.js +4 -4
- package/dist/router/types.js.map +1 -1
- package/dist/rpc/evm/EvmEventLogsReader.d.ts +19 -27
- package/dist/rpc/evm/EvmEventLogsReader.d.ts.map +1 -1
- package/dist/rpc/evm/EvmEventLogsReader.js +23 -0
- package/dist/rpc/evm/EvmEventLogsReader.js.map +1 -1
- package/dist/rpc/evm/EvmEventLogsReader.test.js +53 -1
- package/dist/rpc/evm/EvmEventLogsReader.test.js.map +1 -1
- package/dist/signers/starknet/starknetjs.d.ts.map +1 -1
- package/dist/signers/starknet/starknetjs.js +5 -1
- package/dist/signers/starknet/starknetjs.js.map +1 -1
- package/dist/signers/turnkeyClient.d.ts +1 -15
- package/dist/signers/turnkeyClient.d.ts.map +1 -1
- package/dist/timelock/types.d.ts +8 -32
- package/dist/timelock/types.d.ts.map +1 -1
- package/dist/token/EvmWarpRouteReader.d.ts.map +1 -1
- package/dist/token/EvmWarpRouteReader.js +36 -19
- package/dist/token/EvmWarpRouteReader.js.map +1 -1
- package/dist/token/EvmWarpRouteReader.test.js +219 -2
- package/dist/token/EvmWarpRouteReader.test.js.map +1 -1
- package/dist/token/EvmXERC20Reader.d.ts +9 -21
- package/dist/token/EvmXERC20Reader.d.ts.map +1 -1
- package/dist/token/EvmXERC20Reader.js +24 -48
- package/dist/token/EvmXERC20Reader.js.map +1 -1
- package/dist/token/EvmXERC20Reader.test.js +97 -0
- package/dist/token/EvmXERC20Reader.test.js.map +1 -1
- package/dist/token/ITokenMetadata.d.ts +12 -130
- package/dist/token/ITokenMetadata.d.ts.map +1 -1
- package/dist/token/ITokenMetadata.js +2 -2
- package/dist/token/ITokenMetadata.js.map +1 -1
- package/dist/token/TokenConnection.d.ts +3 -35
- package/dist/token/TokenConnection.d.ts.map +1 -1
- package/dist/token/adapters/StarknetTokenAdapter.js +10 -2
- package/dist/token/adapters/StarknetTokenAdapter.js.map +1 -1
- package/dist/token/configUtils.d.ts.map +1 -1
- package/dist/token/configUtils.js +16 -0
- package/dist/token/configUtils.js.map +1 -1
- package/dist/token/configUtils.test.js +57 -10
- package/dist/token/configUtils.test.js.map +1 -1
- package/dist/token/deploy.hardhat-test.js +165 -5
- package/dist/token/deploy.hardhat-test.js.map +1 -1
- package/dist/token/types.d.ts +4012 -22806
- package/dist/token/types.d.ts.map +1 -1
- package/dist/token/types.js +10 -12
- package/dist/token/types.js.map +1 -1
- package/dist/token/xerc20-abi.d.ts +31 -0
- package/dist/token/xerc20-abi.d.ts.map +1 -1
- package/dist/token/xerc20-abi.js +40 -0
- package/dist/token/xerc20-abi.js.map +1 -1
- package/dist/token/xerc20-limits.d.ts +63 -0
- package/dist/token/xerc20-limits.d.ts.map +1 -0
- package/dist/token/xerc20-limits.js +80 -0
- package/dist/token/xerc20-limits.js.map +1 -0
- package/dist/token/xerc20.d.ts +30 -1
- package/dist/token/xerc20.d.ts.map +1 -1
- package/dist/token/xerc20.js +137 -54
- package/dist/token/xerc20.js.map +1 -1
- package/dist/token/xerc20.test.js +461 -169
- package/dist/token/xerc20.test.js.map +1 -1
- package/dist/types.d.ts +6 -36
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/cosmos.d.ts +113 -844
- package/dist/utils/cosmos.d.ts.map +1 -1
- package/dist/utils/cosmos.js +50 -91
- package/dist/utils/cosmos.js.map +1 -1
- package/dist/utils/schemas.d.ts +3 -3
- package/dist/utils/schemas.d.ts.map +1 -1
- package/dist/utils/schemas.js +2 -1
- package/dist/utils/schemas.js.map +1 -1
- package/dist/utils/starknet.js +5 -1
- package/dist/utils/starknet.js.map +1 -1
- package/dist/warp/types.d.ts +25 -367
- package/dist/warp/types.d.ts.map +1 -1
- package/package.json +14 -14
|
@@ -2,16 +2,18 @@ import chai, { expect } from 'chai';
|
|
|
2
2
|
import chaiAsPromised from 'chai-as-promised';
|
|
3
3
|
import { ethers } from 'ethers';
|
|
4
4
|
import sinon from 'sinon';
|
|
5
|
+
import { assert } from '@hyperlane-xyz/utils';
|
|
5
6
|
import { TestChainName } from '../consts/testChains.js';
|
|
7
|
+
import { EIP1967_IMPLEMENTATION_SLOT } from '../deploy/proxy.js';
|
|
6
8
|
import { MultiProvider } from '../providers/MultiProvider.js';
|
|
7
9
|
import { EvmEventLogsReader } from '../rpc/evm/EvmEventLogsReader.js';
|
|
10
|
+
import { viemLogFromGetEventLogsResponse } from '../rpc/evm/utils.js';
|
|
8
11
|
import { XERC20Type } from './types.js';
|
|
9
|
-
import { CONFIGURATION_CHANGED_EVENT_SELECTOR } from './xerc20-abi.js';
|
|
10
|
-
import { deriveXERC20TokenType, getExtraLockBoxConfigs } from './xerc20.js';
|
|
12
|
+
import { BRIDGE_LIMITS_SET_EVENT_SELECTOR, CONFIGURATION_CHANGED_EVENT_SELECTOR, } from './xerc20-abi.js';
|
|
13
|
+
import { UnknownXERC20TypeError, deriveXERC20TokenType, getExtraLockBoxConfigs, latestConfigurationPerBridge, } from './xerc20.js';
|
|
11
14
|
chai.use(chaiAsPromised);
|
|
12
15
|
const PROXY_ADDRESS = '0x1111111111111111111111111111111111111111';
|
|
13
16
|
const IMPLEMENTATION_ADDRESS = '0x2222222222222222222222222222222222222222';
|
|
14
|
-
const PROXY_ADMIN_ADDRESS = '0x3333333333333333333333333333333333333333';
|
|
15
17
|
const setBufferCapSelector = ethers.utils
|
|
16
18
|
.id('setBufferCap(address,uint256)')
|
|
17
19
|
.slice(2, 10)
|
|
@@ -20,10 +22,6 @@ const setLimitsSelector = ethers.utils
|
|
|
20
22
|
.id('setLimits(address,uint256,uint256)')
|
|
21
23
|
.slice(2, 10)
|
|
22
24
|
.toLowerCase();
|
|
23
|
-
// EIP-1967 admin slot read by proxyAdmin() / isProxy().
|
|
24
|
-
const ADMIN_SLOT = '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103';
|
|
25
|
-
// EIP-1967 implementation slot read by proxyImplementation().
|
|
26
|
-
const IMPLEMENTATION_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
|
|
27
25
|
function storageAddress(address) {
|
|
28
26
|
return ethers.utils.hexZeroPad(address, 32);
|
|
29
27
|
}
|
|
@@ -45,15 +43,15 @@ const cases = [
|
|
|
45
43
|
},
|
|
46
44
|
{
|
|
47
45
|
// Proxy bytecode is a delegatecall stub lacking the selectors; the
|
|
48
|
-
// implementation bytecode carries the Velodrome selector.
|
|
46
|
+
// implementation bytecode carries the Velodrome selector. The admin slot is
|
|
47
|
+
// left out so the stub throws if the derivation reads one.
|
|
49
48
|
name: 'inspects the implementation bytecode when the Velo token is behind a proxy',
|
|
50
49
|
code: {
|
|
51
50
|
[PROXY_ADDRESS]: '0xdead',
|
|
52
51
|
[IMPLEMENTATION_ADDRESS]: `0x${setBufferCapSelector}`,
|
|
53
52
|
},
|
|
54
53
|
storage: {
|
|
55
|
-
[
|
|
56
|
-
[IMPLEMENTATION_SLOT]: storageAddress(IMPLEMENTATION_ADDRESS),
|
|
54
|
+
[EIP1967_IMPLEMENTATION_SLOT]: storageAddress(IMPLEMENTATION_ADDRESS),
|
|
57
55
|
},
|
|
58
56
|
expected: { type: XERC20Type.Velo },
|
|
59
57
|
},
|
|
@@ -65,11 +63,48 @@ const cases = [
|
|
|
65
63
|
[IMPLEMENTATION_ADDRESS]: `0x${setLimitsSelector}`,
|
|
66
64
|
},
|
|
67
65
|
storage: {
|
|
68
|
-
[
|
|
69
|
-
[IMPLEMENTATION_SLOT]: storageAddress(IMPLEMENTATION_ADDRESS),
|
|
66
|
+
[EIP1967_IMPLEMENTATION_SLOT]: storageAddress(IMPLEMENTATION_ADDRESS),
|
|
70
67
|
},
|
|
71
68
|
expected: { type: XERC20Type.Standard },
|
|
72
69
|
},
|
|
70
|
+
{
|
|
71
|
+
// A UUPS proxy keeps its upgrade logic in the implementation, so its admin
|
|
72
|
+
// slot is empty. Reading the implementation off an admin would skip the
|
|
73
|
+
// only bytecode carrying the selectors; omitting the admin slot here makes
|
|
74
|
+
// the stub throw if it is consulted.
|
|
75
|
+
name: 'inspects the implementation bytecode of a Velo token behind a UUPS proxy',
|
|
76
|
+
code: {
|
|
77
|
+
[PROXY_ADDRESS]: '0xdead',
|
|
78
|
+
[IMPLEMENTATION_ADDRESS]: `0x${setBufferCapSelector}`,
|
|
79
|
+
},
|
|
80
|
+
storage: {
|
|
81
|
+
[EIP1967_IMPLEMENTATION_SLOT]: storageAddress(IMPLEMENTATION_ADDRESS),
|
|
82
|
+
},
|
|
83
|
+
expected: { type: XERC20Type.Velo },
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'inspects the implementation bytecode of a Standard token behind a UUPS proxy',
|
|
87
|
+
code: {
|
|
88
|
+
[PROXY_ADDRESS]: '0xdead',
|
|
89
|
+
[IMPLEMENTATION_ADDRESS]: `0x${setLimitsSelector}`,
|
|
90
|
+
},
|
|
91
|
+
storage: {
|
|
92
|
+
[EIP1967_IMPLEMENTATION_SLOT]: storageAddress(IMPLEMENTATION_ADDRESS),
|
|
93
|
+
},
|
|
94
|
+
expected: { type: XERC20Type.Standard },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
// Nothing behind the address to inspect, so the zero implementation must
|
|
98
|
+
// not be fetched as though it were one.
|
|
99
|
+
name: 'throws when the implementation slot is empty',
|
|
100
|
+
code: { [PROXY_ADDRESS]: '0xdead' },
|
|
101
|
+
storage: {
|
|
102
|
+
[EIP1967_IMPLEMENTATION_SLOT]: storageAddress(ethers.constants.AddressZero),
|
|
103
|
+
},
|
|
104
|
+
expected: {
|
|
105
|
+
errorIncludes: 'does not implement Standard or Velodrome XERC20 interface',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
73
108
|
{
|
|
74
109
|
// Proxy resolves to an implementation that still lacks both selectors.
|
|
75
110
|
name: 'throws when neither the proxy nor its implementation implements a known interface',
|
|
@@ -78,8 +113,7 @@ const cases = [
|
|
|
78
113
|
[IMPLEMENTATION_ADDRESS]: '0xbeef',
|
|
79
114
|
},
|
|
80
115
|
storage: {
|
|
81
|
-
[
|
|
82
|
-
[IMPLEMENTATION_SLOT]: storageAddress(IMPLEMENTATION_ADDRESS),
|
|
116
|
+
[EIP1967_IMPLEMENTATION_SLOT]: storageAddress(IMPLEMENTATION_ADDRESS),
|
|
83
117
|
},
|
|
84
118
|
expected: {
|
|
85
119
|
errorIncludes: 'does not implement Standard or Velodrome XERC20 interface',
|
|
@@ -144,12 +178,64 @@ describe('deriveXERC20TokenType', () => {
|
|
|
144
178
|
});
|
|
145
179
|
}
|
|
146
180
|
});
|
|
181
|
+
describe('UnknownXERC20TypeError', () => {
|
|
182
|
+
let sandbox;
|
|
183
|
+
let multiProvider;
|
|
184
|
+
beforeEach(() => {
|
|
185
|
+
sandbox = sinon.createSandbox();
|
|
186
|
+
multiProvider = MultiProvider.createTestMultiProvider();
|
|
187
|
+
});
|
|
188
|
+
afterEach(() => {
|
|
189
|
+
sandbox.restore();
|
|
190
|
+
});
|
|
191
|
+
// Callers tell an undetectable interface apart from a failure to read the
|
|
192
|
+
// bytecode by the error type, so the type is part of the contract.
|
|
193
|
+
it('is thrown when the contract implements neither known interface', async () => {
|
|
194
|
+
const provider = multiProvider.getProvider(TestChainName.test1);
|
|
195
|
+
sandbox.stub(provider, 'getCode').resolves('0xbeef');
|
|
196
|
+
sandbox
|
|
197
|
+
.stub(provider, 'getStorageAt')
|
|
198
|
+
.resolves(storageAddress(PROXY_ADDRESS));
|
|
199
|
+
let thrown;
|
|
200
|
+
try {
|
|
201
|
+
await deriveXERC20TokenType(multiProvider, TestChainName.test1, PROXY_ADDRESS);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
thrown = error;
|
|
205
|
+
}
|
|
206
|
+
expect(thrown).to.be.instanceOf(UnknownXERC20TypeError);
|
|
207
|
+
});
|
|
208
|
+
it('is not thrown when the bytecode cannot be read at all', async () => {
|
|
209
|
+
const provider = multiProvider.getProvider(TestChainName.test1);
|
|
210
|
+
const transientError = new Error('Invalid response from provider');
|
|
211
|
+
sandbox.stub(provider, 'getCode').rejects(transientError);
|
|
212
|
+
let thrown;
|
|
213
|
+
try {
|
|
214
|
+
await deriveXERC20TokenType(multiProvider, TestChainName.test1, PROXY_ADDRESS);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
thrown = error;
|
|
218
|
+
}
|
|
219
|
+
expect(thrown).to.equal(transientError);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
147
222
|
const XERC20_ADDRESS = '0x6666666666666666666666666666666666666666';
|
|
148
223
|
const BRIDGE_A = '0x4444444444444444444444444444444444444444';
|
|
149
224
|
const BRIDGE_B = '0x5555555555555555555555555555555555555555';
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
225
|
+
// A mainnet warp route router, which its xERC20 holds bridge limits for like
|
|
226
|
+
// any other bridge. Those limits are reported as warpRouteLimits, never as an
|
|
227
|
+
// extra bridge.
|
|
228
|
+
const WARP_ROUTER = '0x88AC0fC430130983c0DDEB4C22574056D8340Ca8';
|
|
229
|
+
const rateLimitsSelector = ethers.utils.id('rateLimits(address)').slice(0, 10);
|
|
230
|
+
const mintingMaxLimitOfSelector = ethers.utils
|
|
231
|
+
.id('mintingMaxLimitOf(address)')
|
|
232
|
+
.slice(0, 10);
|
|
233
|
+
const burningMaxLimitOfSelector = ethers.utils
|
|
234
|
+
.id('burningMaxLimitOf(address)')
|
|
235
|
+
.slice(0, 10);
|
|
236
|
+
// The scan reads the bridge out of topics[1] and nothing else, so the payload
|
|
237
|
+
// only matters to latestConfigurationPerBridge, which parses it.
|
|
238
|
+
function configurationChangedLog({ bridge, bufferCap = 100, rateLimitPerSecond = 1, blockNumber, logIndex = 0, }) {
|
|
153
239
|
return {
|
|
154
240
|
address: XERC20_ADDRESS,
|
|
155
241
|
blockNumber,
|
|
@@ -163,152 +249,112 @@ function configurationChangedLog({ bridge, bufferCap, rateLimitPerSecond, blockN
|
|
|
163
249
|
transactionIndex: 0,
|
|
164
250
|
};
|
|
165
251
|
}
|
|
166
|
-
|
|
167
|
-
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}),
|
|
176
|
-
configurationChangedLog({
|
|
177
|
-
bridge: BRIDGE_A,
|
|
178
|
-
bufferCap: 500,
|
|
179
|
-
rateLimitPerSecond: 2,
|
|
180
|
-
blockNumber: 20,
|
|
181
|
-
}),
|
|
252
|
+
function bridgeLimitsSetLog({ bridge, blockNumber, logIndex = 0, }) {
|
|
253
|
+
return {
|
|
254
|
+
address: XERC20_ADDRESS,
|
|
255
|
+
blockNumber,
|
|
256
|
+
data: ethers.utils.defaultAbiCoder.encode(['uint256', 'uint256'], [100, 100]),
|
|
257
|
+
logIndex,
|
|
258
|
+
topics: [
|
|
259
|
+
BRIDGE_LIMITS_SET_EVENT_SELECTOR,
|
|
260
|
+
ethers.utils.hexZeroPad(bridge, 32),
|
|
182
261
|
],
|
|
183
|
-
|
|
262
|
+
transactionHash: ethers.utils.hexZeroPad(`0x${blockNumber}`, 32),
|
|
263
|
+
transactionIndex: 0,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const bridgeCases = [
|
|
267
|
+
{
|
|
268
|
+
// Every non-lockbox bridge used to be dropped, because the reader kept only
|
|
269
|
+
// the addresses answering the lockbox XERC20() getter.
|
|
270
|
+
name: 'reports a configured bridge that is not a lockbox',
|
|
271
|
+
type: XERC20Type.Velo,
|
|
272
|
+
logs: [configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 10 })],
|
|
273
|
+
onChainLimits: {
|
|
274
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: '100', rateLimitPerSecond: '1' },
|
|
275
|
+
},
|
|
184
276
|
expected: [
|
|
185
277
|
{
|
|
186
|
-
lockbox: BRIDGE_A,
|
|
278
|
+
lockbox: BRIDGE_A.toLowerCase(),
|
|
187
279
|
limits: {
|
|
188
280
|
type: XERC20Type.Velo,
|
|
189
|
-
bufferCap: '
|
|
190
|
-
rateLimitPerSecond: '
|
|
281
|
+
bufferCap: '100',
|
|
282
|
+
rateLimitPerSecond: '1',
|
|
191
283
|
},
|
|
192
284
|
},
|
|
193
285
|
],
|
|
194
286
|
},
|
|
195
287
|
{
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
name:
|
|
288
|
+
// The route's router holds bridge limits like any other bridge, and the
|
|
289
|
+
// lockbox probe used to be what kept it out of the extra bridges.
|
|
290
|
+
name: "omits the warp route's own router",
|
|
291
|
+
type: XERC20Type.Velo,
|
|
199
292
|
logs: [
|
|
200
|
-
configurationChangedLog({
|
|
201
|
-
|
|
202
|
-
bufferCap: 100,
|
|
203
|
-
rateLimitPerSecond: 1,
|
|
204
|
-
blockNumber: 10,
|
|
205
|
-
logIndex: 3,
|
|
206
|
-
}),
|
|
207
|
-
configurationChangedLog({
|
|
208
|
-
bridge: BRIDGE_A,
|
|
209
|
-
bufferCap: 500,
|
|
210
|
-
rateLimitPerSecond: 2,
|
|
211
|
-
blockNumber: 10,
|
|
212
|
-
logIndex: 7,
|
|
213
|
-
}),
|
|
293
|
+
configurationChangedLog({ bridge: WARP_ROUTER, blockNumber: 10 }),
|
|
294
|
+
configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 11 }),
|
|
214
295
|
],
|
|
215
|
-
|
|
296
|
+
warpRouteAddress: WARP_ROUTER,
|
|
297
|
+
onChainLimits: {
|
|
298
|
+
[WARP_ROUTER.toLowerCase()]: {
|
|
299
|
+
bufferCap: '300',
|
|
300
|
+
rateLimitPerSecond: '3',
|
|
301
|
+
},
|
|
302
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: '100', rateLimitPerSecond: '1' },
|
|
303
|
+
},
|
|
216
304
|
expected: [
|
|
217
305
|
{
|
|
218
|
-
lockbox: BRIDGE_A,
|
|
306
|
+
lockbox: BRIDGE_A.toLowerCase(),
|
|
219
307
|
limits: {
|
|
220
308
|
type: XERC20Type.Velo,
|
|
221
|
-
bufferCap: '
|
|
222
|
-
rateLimitPerSecond: '
|
|
309
|
+
bufferCap: '100',
|
|
310
|
+
rateLimitPerSecond: '1',
|
|
223
311
|
},
|
|
224
312
|
},
|
|
225
313
|
],
|
|
226
314
|
},
|
|
227
315
|
{
|
|
228
|
-
// The
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
logIndex: 7,
|
|
237
|
-
}),
|
|
238
|
-
configurationChangedLog({
|
|
239
|
-
bridge: BRIDGE_A,
|
|
240
|
-
bufferCap: 100,
|
|
241
|
-
rateLimitPerSecond: 1,
|
|
242
|
-
blockNumber: 10,
|
|
243
|
-
logIndex: 3,
|
|
244
|
-
}),
|
|
245
|
-
],
|
|
246
|
-
lockboxBridges: [BRIDGE_A],
|
|
316
|
+
// The event is a record of a past announcement, not of the current state:
|
|
317
|
+
// only what the token holds now decides whether a bridge is active.
|
|
318
|
+
name: 'reports a bridge on the limits it holds, not the ones it was announced with',
|
|
319
|
+
type: XERC20Type.Velo,
|
|
320
|
+
logs: [configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 20 })],
|
|
321
|
+
onChainLimits: {
|
|
322
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: '900', rateLimitPerSecond: '9' },
|
|
323
|
+
},
|
|
247
324
|
expected: [
|
|
248
325
|
{
|
|
249
|
-
lockbox: BRIDGE_A,
|
|
326
|
+
lockbox: BRIDGE_A.toLowerCase(),
|
|
250
327
|
limits: {
|
|
251
328
|
type: XERC20Type.Velo,
|
|
252
|
-
bufferCap: '
|
|
253
|
-
rateLimitPerSecond: '
|
|
329
|
+
bufferCap: '900',
|
|
330
|
+
rateLimitPerSecond: '9',
|
|
254
331
|
},
|
|
255
332
|
},
|
|
256
333
|
],
|
|
257
334
|
},
|
|
258
335
|
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
bufferCap: 100,
|
|
266
|
-
rateLimitPerSecond: 1,
|
|
267
|
-
blockNumber: 10,
|
|
268
|
-
}),
|
|
269
|
-
configurationChangedLog({
|
|
270
|
-
bridge: BRIDGE_A,
|
|
271
|
-
bufferCap: 0,
|
|
272
|
-
rateLimitPerSecond: 0,
|
|
273
|
-
blockNumber: 20,
|
|
274
|
-
}),
|
|
275
|
-
],
|
|
276
|
-
lockboxBridges: [BRIDGE_A],
|
|
277
|
-
expected: [],
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
name: 'drops a bridge configured with both limits set to zero',
|
|
281
|
-
logs: [
|
|
282
|
-
configurationChangedLog({
|
|
283
|
-
bridge: BRIDGE_A,
|
|
284
|
-
bufferCap: 0,
|
|
285
|
-
rateLimitPerSecond: 0,
|
|
286
|
-
blockNumber: 10,
|
|
287
|
-
}),
|
|
288
|
-
],
|
|
289
|
-
lockboxBridges: [BRIDGE_A],
|
|
336
|
+
name: 'drops a bridge the token holds no limits for',
|
|
337
|
+
type: XERC20Type.Velo,
|
|
338
|
+
logs: [configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 10 })],
|
|
339
|
+
onChainLimits: {
|
|
340
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: '0', rateLimitPerSecond: '0' },
|
|
341
|
+
},
|
|
290
342
|
expected: [],
|
|
291
343
|
},
|
|
292
344
|
{
|
|
293
|
-
name: 'keeps
|
|
345
|
+
name: 'keeps a bridge where either limit is non zero',
|
|
346
|
+
type: XERC20Type.Velo,
|
|
294
347
|
logs: [
|
|
295
|
-
configurationChangedLog({
|
|
296
|
-
|
|
297
|
-
bufferCap: 0,
|
|
298
|
-
rateLimitPerSecond: 7,
|
|
299
|
-
blockNumber: 10,
|
|
300
|
-
}),
|
|
301
|
-
configurationChangedLog({
|
|
302
|
-
bridge: BRIDGE_B,
|
|
303
|
-
bufferCap: 9,
|
|
304
|
-
rateLimitPerSecond: 0,
|
|
305
|
-
blockNumber: 11,
|
|
306
|
-
}),
|
|
348
|
+
configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 10 }),
|
|
349
|
+
configurationChangedLog({ bridge: BRIDGE_B, blockNumber: 11 }),
|
|
307
350
|
],
|
|
308
|
-
|
|
351
|
+
onChainLimits: {
|
|
352
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: '0', rateLimitPerSecond: '7' },
|
|
353
|
+
[BRIDGE_B.toLowerCase()]: { bufferCap: '9', rateLimitPerSecond: '0' },
|
|
354
|
+
},
|
|
309
355
|
expected: [
|
|
310
356
|
{
|
|
311
|
-
lockbox: BRIDGE_A,
|
|
357
|
+
lockbox: BRIDGE_A.toLowerCase(),
|
|
312
358
|
limits: {
|
|
313
359
|
type: XERC20Type.Velo,
|
|
314
360
|
bufferCap: '0',
|
|
@@ -316,7 +362,7 @@ const lockboxCases = [
|
|
|
316
362
|
},
|
|
317
363
|
},
|
|
318
364
|
{
|
|
319
|
-
lockbox: BRIDGE_B,
|
|
365
|
+
lockbox: BRIDGE_B.toLowerCase(),
|
|
320
366
|
limits: {
|
|
321
367
|
type: XERC20Type.Velo,
|
|
322
368
|
bufferCap: '9',
|
|
@@ -326,25 +372,25 @@ const lockboxCases = [
|
|
|
326
372
|
],
|
|
327
373
|
},
|
|
328
374
|
{
|
|
329
|
-
|
|
375
|
+
// A bridge is announced again every time its limits change, so the same
|
|
376
|
+
// address arrives many times over a token's history.
|
|
377
|
+
name: 'reports a bridge announced more than once only once',
|
|
378
|
+
type: XERC20Type.Velo,
|
|
330
379
|
logs: [
|
|
380
|
+
configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 10 }),
|
|
381
|
+
configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 20 }),
|
|
331
382
|
configurationChangedLog({
|
|
332
383
|
bridge: BRIDGE_A,
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
blockNumber: 10,
|
|
336
|
-
}),
|
|
337
|
-
configurationChangedLog({
|
|
338
|
-
bridge: BRIDGE_B,
|
|
339
|
-
bufferCap: 200,
|
|
340
|
-
rateLimitPerSecond: 2,
|
|
341
|
-
blockNumber: 11,
|
|
384
|
+
blockNumber: 20,
|
|
385
|
+
logIndex: 4,
|
|
342
386
|
}),
|
|
343
387
|
],
|
|
344
|
-
|
|
388
|
+
onChainLimits: {
|
|
389
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: '100', rateLimitPerSecond: '1' },
|
|
390
|
+
},
|
|
345
391
|
expected: [
|
|
346
392
|
{
|
|
347
|
-
lockbox: BRIDGE_A,
|
|
393
|
+
lockbox: BRIDGE_A.toLowerCase(),
|
|
348
394
|
limits: {
|
|
349
395
|
type: XERC20Type.Velo,
|
|
350
396
|
bufferCap: '100',
|
|
@@ -354,78 +400,220 @@ const lockboxCases = [
|
|
|
354
400
|
],
|
|
355
401
|
},
|
|
356
402
|
{
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
403
|
+
// A Standard token announces its bridges through a different event and
|
|
404
|
+
// exposes no rateLimits getter, so it used to be invisible on both counts.
|
|
405
|
+
name: 'discovers and reports the bridges of a Standard xERC20',
|
|
406
|
+
type: XERC20Type.Standard,
|
|
407
|
+
logs: [
|
|
408
|
+
bridgeLimitsSetLog({ bridge: BRIDGE_A, blockNumber: 10 }),
|
|
409
|
+
bridgeLimitsSetLog({ bridge: BRIDGE_B, blockNumber: 11 }),
|
|
410
|
+
],
|
|
411
|
+
onChainLimits: {
|
|
412
|
+
[BRIDGE_A.toLowerCase()]: { mint: '400', burn: '500' },
|
|
413
|
+
[BRIDGE_B.toLowerCase()]: { mint: '0', burn: '0' },
|
|
414
|
+
},
|
|
415
|
+
expected: [
|
|
416
|
+
{
|
|
417
|
+
lockbox: BRIDGE_A.toLowerCase(),
|
|
418
|
+
limits: { type: XERC20Type.Standard, mint: '400', burn: '500' },
|
|
419
|
+
},
|
|
420
|
+
],
|
|
361
421
|
},
|
|
362
422
|
];
|
|
423
|
+
const DEPLOYMENT_BLOCK = 5755967;
|
|
363
424
|
describe('getExtraLockBoxConfigs', () => {
|
|
364
425
|
let sandbox;
|
|
365
426
|
let multiProvider;
|
|
427
|
+
let deploymentBlock;
|
|
366
428
|
beforeEach(() => {
|
|
367
429
|
sandbox = sinon.createSandbox();
|
|
368
430
|
multiProvider = MultiProvider.createTestMultiProvider();
|
|
431
|
+
deploymentBlock = sandbox
|
|
432
|
+
.stub(EvmEventLogsReader.prototype, 'getContractDeploymentBlock')
|
|
433
|
+
.resolves(DEPLOYMENT_BLOCK);
|
|
369
434
|
});
|
|
370
435
|
afterEach(() => {
|
|
371
436
|
sandbox.restore();
|
|
372
437
|
});
|
|
373
|
-
|
|
374
|
-
|
|
438
|
+
// Serves the token's bytecode, which is what the type derivation reads, and
|
|
439
|
+
// the limit getter of that type. An address the token holds no limits for
|
|
440
|
+
// reverts with empty return data, like a getter reading an unset entry on a
|
|
441
|
+
// contract that does not have it.
|
|
442
|
+
function stubXERC20(type, onChainLimits) {
|
|
375
443
|
const provider = multiProvider.getProvider(TestChainName.test1);
|
|
444
|
+
sandbox
|
|
445
|
+
.stub(provider, 'getCode')
|
|
446
|
+
.resolves(`0x${type === XERC20Type.Velo ? setBufferCapSelector : setLimitsSelector}`);
|
|
376
447
|
sandbox.stub(provider, 'call').callsFake(async (transaction) => {
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
448
|
+
const data = await transaction.data;
|
|
449
|
+
assert(typeof data === 'string', 'Expected call data');
|
|
450
|
+
const selector = data.slice(0, 10);
|
|
451
|
+
const [bridge] = ethers.utils.defaultAbiCoder.decode(['address'], `0x${data.slice(10)}`);
|
|
452
|
+
const limits = onChainLimits[bridge.toLowerCase()];
|
|
453
|
+
if (!limits) {
|
|
454
|
+
throw Object.assign(new Error('call revert exception'), {
|
|
455
|
+
code: 'CALL_EXCEPTION',
|
|
456
|
+
data: '0x',
|
|
457
|
+
});
|
|
380
458
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
459
|
+
if (selector === rateLimitsSelector && 'bufferCap' in limits) {
|
|
460
|
+
return ethers.utils.defaultAbiCoder.encode(['tuple(uint128,uint112,uint32,uint112,uint112)'], [[limits.rateLimitPerSecond, limits.bufferCap, 0, 0, 0]]);
|
|
461
|
+
}
|
|
462
|
+
if (selector === mintingMaxLimitOfSelector && 'mint' in limits) {
|
|
463
|
+
return ethers.utils.defaultAbiCoder.encode(['uint256'], [limits.mint]);
|
|
464
|
+
}
|
|
465
|
+
if (selector === burningMaxLimitOfSelector && 'burn' in limits) {
|
|
466
|
+
return ethers.utils.defaultAbiCoder.encode(['uint256'], [limits.burn]);
|
|
467
|
+
}
|
|
468
|
+
throw new Error(`Unexpected call ${selector} for ${bridge}`);
|
|
385
469
|
});
|
|
386
470
|
}
|
|
387
|
-
for (const c of
|
|
471
|
+
for (const c of bridgeCases) {
|
|
388
472
|
it(c.name, async () => {
|
|
389
473
|
sandbox
|
|
390
474
|
.stub(EvmEventLogsReader.prototype, 'getLogsByTopic')
|
|
391
475
|
.resolves(c.logs);
|
|
392
|
-
|
|
476
|
+
stubXERC20(c.type, c.onChainLimits);
|
|
393
477
|
const result = await getExtraLockBoxConfigs({
|
|
394
478
|
chain: TestChainName.test1,
|
|
395
479
|
xERC20Address: XERC20_ADDRESS,
|
|
396
480
|
multiProvider,
|
|
481
|
+
warpRouteAddress: c.warpRouteAddress,
|
|
397
482
|
});
|
|
398
483
|
expect(result).to.deep.equal(c.expected);
|
|
399
484
|
});
|
|
400
485
|
}
|
|
401
|
-
|
|
486
|
+
// Querying the wrong signature's topic returns nothing, which reads as a
|
|
487
|
+
// token with no bridges rather than as a scan that asked the wrong question.
|
|
488
|
+
const topicCases = [
|
|
489
|
+
{
|
|
490
|
+
name: 'scans the Velodrome event topic for a Velodrome xERC20',
|
|
491
|
+
type: XERC20Type.Velo,
|
|
492
|
+
expectedTopic: '0xb4ff6a860e04455b1ce16833b74cde19765c95e55c5e7e4f5a69e9707d8cc96d',
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: 'scans the Standard event topic for a Standard xERC20',
|
|
496
|
+
type: XERC20Type.Standard,
|
|
497
|
+
expectedTopic: '0x93f3bbfe8cfb354ec059175107653f49f6eb479a8622a7d83866ea015435c944',
|
|
498
|
+
},
|
|
499
|
+
];
|
|
500
|
+
for (const c of topicCases) {
|
|
501
|
+
it(c.name, async () => {
|
|
502
|
+
const fromConfig = sandbox.spy(EvmEventLogsReader, 'fromConfig');
|
|
503
|
+
const getLogsByTopic = sandbox
|
|
504
|
+
.stub(EvmEventLogsReader.prototype, 'getLogsByTopic')
|
|
505
|
+
.resolves([
|
|
506
|
+
configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 10 }),
|
|
507
|
+
]);
|
|
508
|
+
// Non-empty so the read settles on the first answer; what it asked for is
|
|
509
|
+
// what this pins.
|
|
510
|
+
stubXERC20(c.type, {
|
|
511
|
+
[BRIDGE_A.toLowerCase()]: c.type === XERC20Type.Velo
|
|
512
|
+
? { bufferCap: '1', rateLimitPerSecond: '1' }
|
|
513
|
+
: { mint: '1', burn: '1' },
|
|
514
|
+
});
|
|
515
|
+
await getExtraLockBoxConfigs({
|
|
516
|
+
chain: TestChainName.test1,
|
|
517
|
+
xERC20Address: XERC20_ADDRESS,
|
|
518
|
+
multiProvider,
|
|
519
|
+
});
|
|
520
|
+
expect(fromConfig.calledOnce).to.be.true;
|
|
521
|
+
expect(fromConfig.firstCall.args[0]).to.deep.equal({
|
|
522
|
+
chain: TestChainName.test1,
|
|
523
|
+
paginationBlockRange: 1_000_000,
|
|
524
|
+
});
|
|
525
|
+
expect(getLogsByTopic.calledOnceWithExactly({
|
|
526
|
+
contractAddress: XERC20_ADDRESS,
|
|
527
|
+
eventTopic: c.expectedTopic,
|
|
528
|
+
fromBlock: DEPLOYMENT_BLOCK,
|
|
529
|
+
})).to.be.true;
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
// A route's token holds limits for at least the route's own router, so it has
|
|
533
|
+
// announced at least one bridge: an explorer answering with none has failed to
|
|
534
|
+
// answer, which is indistinguishable from a token that has none until the RPC
|
|
535
|
+
// is asked. Two of the six oUSDT chains report exactly this way.
|
|
536
|
+
it('re-reads over the RPC when the explorer announces no bridges', async () => {
|
|
402
537
|
const fromConfig = sandbox.spy(EvmEventLogsReader, 'fromConfig');
|
|
403
|
-
const getLogsByTopic = sandbox
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
538
|
+
const getLogsByTopic = sandbox.stub(EvmEventLogsReader.prototype, 'getLogsByTopic');
|
|
539
|
+
getLogsByTopic.onFirstCall().resolves([]);
|
|
540
|
+
getLogsByTopic
|
|
541
|
+
.onSecondCall()
|
|
542
|
+
.resolves([
|
|
543
|
+
configurationChangedLog({ bridge: BRIDGE_A, blockNumber: 10 }),
|
|
544
|
+
]);
|
|
545
|
+
stubXERC20(XERC20Type.Velo, {
|
|
546
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: '100', rateLimitPerSecond: '1' },
|
|
547
|
+
});
|
|
548
|
+
const result = await getExtraLockBoxConfigs({
|
|
408
549
|
chain: TestChainName.test1,
|
|
409
550
|
xERC20Address: XERC20_ADDRESS,
|
|
410
551
|
multiProvider,
|
|
411
552
|
});
|
|
412
|
-
|
|
413
|
-
|
|
553
|
+
// The deployment block is resolved once and handed to both reads. Letting
|
|
554
|
+
// the RPC reader derive it is what fails on a chain that serves no archive
|
|
555
|
+
// state, so a second resolution here is the defect this guards.
|
|
556
|
+
expect(deploymentBlock.calledOnce).to.be.true;
|
|
557
|
+
const query = {
|
|
558
|
+
contractAddress: XERC20_ADDRESS,
|
|
559
|
+
eventTopic: '0xb4ff6a860e04455b1ce16833b74cde19765c95e55c5e7e4f5a69e9707d8cc96d',
|
|
560
|
+
fromBlock: DEPLOYMENT_BLOCK,
|
|
561
|
+
};
|
|
562
|
+
expect(getLogsByTopic.calledTwice).to.be.true;
|
|
563
|
+
expect(getLogsByTopic.firstCall.args[0]).to.deep.equal(query);
|
|
564
|
+
expect(getLogsByTopic.secondCall.args[0]).to.deep.equal(query);
|
|
565
|
+
expect(fromConfig.calledTwice).to.be.true;
|
|
566
|
+
expect(fromConfig.secondCall.args[0]).to.deep.equal({
|
|
414
567
|
chain: TestChainName.test1,
|
|
415
568
|
paginationBlockRange: 1_000_000,
|
|
569
|
+
useRPC: true,
|
|
416
570
|
});
|
|
417
|
-
expect(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
571
|
+
expect(result).to.deep.equal([
|
|
572
|
+
{
|
|
573
|
+
lockbox: BRIDGE_A.toLowerCase(),
|
|
574
|
+
limits: {
|
|
575
|
+
type: XERC20Type.Velo,
|
|
576
|
+
bufferCap: '100',
|
|
577
|
+
rateLimitPerSecond: '1',
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
]);
|
|
581
|
+
});
|
|
582
|
+
// The RPC is the reader's own fallback when a chain declares no explorer, so
|
|
583
|
+
// re-reading there would scan the token's whole history a second time to
|
|
584
|
+
// reach the answer it already gave.
|
|
585
|
+
it('does not re-read when the chain declares no block explorer', async () => {
|
|
586
|
+
sandbox.stub(multiProvider, 'tryGetEvmExplorerMetadataList').returns([]);
|
|
587
|
+
const fromConfig = sandbox.spy(EvmEventLogsReader, 'fromConfig');
|
|
588
|
+
sandbox.stub(EvmEventLogsReader.prototype, 'getLogsByTopic').resolves([]);
|
|
589
|
+
stubXERC20(XERC20Type.Velo, {});
|
|
590
|
+
const result = await getExtraLockBoxConfigs({
|
|
591
|
+
chain: TestChainName.test1,
|
|
592
|
+
xERC20Address: XERC20_ADDRESS,
|
|
593
|
+
multiProvider,
|
|
594
|
+
});
|
|
595
|
+
expect(fromConfig.calledOnce).to.be.true;
|
|
596
|
+
expect(result).to.deep.equal([]);
|
|
597
|
+
});
|
|
598
|
+
it('propagates an RPC re-read failure', async () => {
|
|
599
|
+
const getLogsByTopic = sandbox.stub(EvmEventLogsReader.prototype, 'getLogsByTopic');
|
|
600
|
+
getLogsByTopic.onFirstCall().resolves([]);
|
|
601
|
+
getLogsByTopic.onSecondCall().rejects(new Error('rpc down'));
|
|
602
|
+
stubXERC20(XERC20Type.Velo, {});
|
|
603
|
+
await expect(getExtraLockBoxConfigs({
|
|
604
|
+
chain: TestChainName.test1,
|
|
605
|
+
xERC20Address: XERC20_ADDRESS,
|
|
606
|
+
multiProvider,
|
|
607
|
+
})).to.be.rejectedWith('rpc down');
|
|
421
608
|
});
|
|
422
609
|
// A failed log read used to be swallowed into an empty list, which reported
|
|
423
|
-
// a token with extra
|
|
424
|
-
|
|
610
|
+
// a token with extra bridges as having none. The events are the only place
|
|
611
|
+
// the bridge addresses exist, so a failure to read them is the answer.
|
|
612
|
+
it('propagates a log read failure', async () => {
|
|
425
613
|
sandbox
|
|
426
614
|
.stub(EvmEventLogsReader.prototype, 'getLogsByTopic')
|
|
427
615
|
.rejects(new Error('rpc down'));
|
|
428
|
-
|
|
616
|
+
stubXERC20(XERC20Type.Velo, {});
|
|
429
617
|
await expect(getExtraLockBoxConfigs({
|
|
430
618
|
chain: TestChainName.test1,
|
|
431
619
|
xERC20Address: XERC20_ADDRESS,
|
|
@@ -433,4 +621,108 @@ describe('getExtraLockBoxConfigs', () => {
|
|
|
433
621
|
})).to.be.rejectedWith('rpc down');
|
|
434
622
|
});
|
|
435
623
|
});
|
|
624
|
+
describe('latestConfigurationPerBridge', () => {
|
|
625
|
+
const orderingCases = [
|
|
626
|
+
{
|
|
627
|
+
name: 'keeps only the most recent configuration of a bridge',
|
|
628
|
+
logs: [
|
|
629
|
+
configurationChangedLog({
|
|
630
|
+
bridge: BRIDGE_A,
|
|
631
|
+
bufferCap: 100,
|
|
632
|
+
rateLimitPerSecond: 1,
|
|
633
|
+
blockNumber: 10,
|
|
634
|
+
}),
|
|
635
|
+
configurationChangedLog({
|
|
636
|
+
bridge: BRIDGE_A,
|
|
637
|
+
bufferCap: 500,
|
|
638
|
+
rateLimitPerSecond: 2,
|
|
639
|
+
blockNumber: 20,
|
|
640
|
+
}),
|
|
641
|
+
],
|
|
642
|
+
expected: {
|
|
643
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: 500n, rateLimitPerSecond: 2n },
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
// A bridge can be reconfigured twice in the same block, so ordering on the
|
|
648
|
+
// block number alone kept the first configuration instead of the last.
|
|
649
|
+
name: 'keeps the highest logIndex configuration of a bridge within a block',
|
|
650
|
+
logs: [
|
|
651
|
+
configurationChangedLog({
|
|
652
|
+
bridge: BRIDGE_A,
|
|
653
|
+
bufferCap: 100,
|
|
654
|
+
rateLimitPerSecond: 1,
|
|
655
|
+
blockNumber: 10,
|
|
656
|
+
logIndex: 3,
|
|
657
|
+
}),
|
|
658
|
+
configurationChangedLog({
|
|
659
|
+
bridge: BRIDGE_A,
|
|
660
|
+
bufferCap: 500,
|
|
661
|
+
rateLimitPerSecond: 2,
|
|
662
|
+
blockNumber: 10,
|
|
663
|
+
logIndex: 7,
|
|
664
|
+
}),
|
|
665
|
+
],
|
|
666
|
+
expected: {
|
|
667
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: 500n, rateLimitPerSecond: 2n },
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
// The same-block tiebreaker must not depend on the order the logs arrive in
|
|
672
|
+
name: 'keeps the highest logIndex configuration when logs arrive out of order',
|
|
673
|
+
logs: [
|
|
674
|
+
configurationChangedLog({
|
|
675
|
+
bridge: BRIDGE_A,
|
|
676
|
+
bufferCap: 500,
|
|
677
|
+
rateLimitPerSecond: 2,
|
|
678
|
+
blockNumber: 10,
|
|
679
|
+
logIndex: 7,
|
|
680
|
+
}),
|
|
681
|
+
configurationChangedLog({
|
|
682
|
+
bridge: BRIDGE_A,
|
|
683
|
+
bufferCap: 100,
|
|
684
|
+
rateLimitPerSecond: 1,
|
|
685
|
+
blockNumber: 10,
|
|
686
|
+
logIndex: 3,
|
|
687
|
+
}),
|
|
688
|
+
],
|
|
689
|
+
expected: {
|
|
690
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: 500n, rateLimitPerSecond: 2n },
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
name: 'keeps every bridge that was configured',
|
|
695
|
+
logs: [
|
|
696
|
+
configurationChangedLog({
|
|
697
|
+
bridge: BRIDGE_A,
|
|
698
|
+
bufferCap: 100,
|
|
699
|
+
rateLimitPerSecond: 1,
|
|
700
|
+
blockNumber: 10,
|
|
701
|
+
}),
|
|
702
|
+
configurationChangedLog({
|
|
703
|
+
bridge: BRIDGE_B,
|
|
704
|
+
bufferCap: 200,
|
|
705
|
+
rateLimitPerSecond: 2,
|
|
706
|
+
blockNumber: 11,
|
|
707
|
+
}),
|
|
708
|
+
],
|
|
709
|
+
expected: {
|
|
710
|
+
[BRIDGE_A.toLowerCase()]: { bufferCap: 100n, rateLimitPerSecond: 1n },
|
|
711
|
+
[BRIDGE_B.toLowerCase()]: { bufferCap: 200n, rateLimitPerSecond: 2n },
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
];
|
|
715
|
+
for (const c of orderingCases) {
|
|
716
|
+
it(c.name, () => {
|
|
717
|
+
const latest = latestConfigurationPerBridge(c.logs.map(viemLogFromGetEventLogsResponse));
|
|
718
|
+
expect([...latest.keys()]).to.have.members(Object.keys(c.expected));
|
|
719
|
+
for (const [bridge, limits] of Object.entries(c.expected)) {
|
|
720
|
+
const log = latest.get(bridge);
|
|
721
|
+
assert(log, `Missing configuration for ${bridge}`);
|
|
722
|
+
expect(log.args.bufferCap).to.equal(limits.bufferCap);
|
|
723
|
+
expect(log.args.rateLimitPerSecond).to.equal(limits.rateLimitPerSecond);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
});
|
|
436
728
|
//# sourceMappingURL=xerc20.test.js.map
|