From a652291f41311b8b621b0e364c307526e1d8ffc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Mon, 25 Mar 2024 16:53:18 +0100 Subject: [PATCH 01/33] add erc20 oeth bridge contract --- governance/contracts/l2/UDTOptimism.sol | 103 ++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 governance/contracts/l2/UDTOptimism.sol diff --git a/governance/contracts/l2/UDTOptimism.sol b/governance/contracts/l2/UDTOptimism.sol new file mode 100644 index 00000000000..5a5f5057f70 --- /dev/null +++ b/governance/contracts/l2/UDTOptimism.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/// @title IOptimismMintableERC20 +/// @notice This interface is available on the OptimismMintableERC20 contract. +/// We declare it as a separate interface so that it can be used in +/// custom implementations of OptimismMintableERC20. +interface IOptimismMintableERC20 is IERC165 { + function remoteToken() external view returns (address); + + function bridge() external returns (address); + + function mint(address _to, uint256 _amount) external; + + function burn(address _from, uint256 _amount) external; +} + +contract UDTOptimism is IOptimismMintableERC20, ERC20 { + /// @notice Address of the corresponding version of this token on the remote chain. + address public immutable REMOTE_TOKEN; + + /// @notice Address of the StandardBridge on this network. + address public immutable BRIDGE; + + /// @notice Emitted whenever tokens are minted for an account. + /// @param account Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + event Mint(address indexed account, uint256 amount); + + /// @notice Emitted whenever tokens are burned from an account. + /// @param account Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + event Burn(address indexed account, uint256 amount); + + /// @notice A modifier that only allows the bridge to call. + modifier onlyBridge() { + require( + msg.sender == BRIDGE, + "UnlockDiscountToken: only bridge can mint and burn" + ); + _; + } + + /// @param _bridge Address of the L2 standard bridge. + /// @param _remoteToken Address of the corresponding L1 token. + /// @param _name ERC20 name. + /// @param _symbol ERC20 symbol. + constructor( + address _bridge, + address _remoteToken, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol) { + REMOTE_TOKEN = _remoteToken; + BRIDGE = _bridge; + } + + /// @custom:legacy + /// @notice Legacy getter for REMOTE_TOKEN. + function remoteToken() public view returns (address) { + return REMOTE_TOKEN; + } + + /// @custom:legacy + /// @notice Legacy getter for BRIDGE. + function bridge() public view returns (address) { + return BRIDGE; + } + + /// @notice ERC165 interface check function. + /// @param _interfaceId Interface ID to check. + /// @return Whether or not the interface is supported by this contract. + function supportsInterface( + bytes4 _interfaceId + ) external pure virtual returns (bool) { + bytes4 iface1 = type(IERC165).interfaceId; + // Interface corresponding to the updated OptimismMintableERC20 (this contract). + bytes4 iface2 = type(IOptimismMintableERC20).interfaceId; + return _interfaceId == iface1 || _interfaceId == iface2; + } + + /// @notice Allows the StandardBridge on this network to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function mint( + address _to, + uint256 _amount + ) external virtual override(IOptimismMintableERC20) onlyBridge { + _mint(_to, _amount); + emit Mint(_to, _amount); + } + + /// @notice Prevents tokens from being withdrawn to L1. + function burn( + address, + uint256 + ) external virtual override(IOptimismMintableERC20) onlyBridge { + revert("UnlockDiscountToken cannot be withdrawn"); + } +} From 5081d81b72f8d5ccdce6fc1753e3d2ef4712de1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Mon, 25 Mar 2024 16:53:44 +0100 Subject: [PATCH 02/33] deploy script --- governance/.gitignore | 4 ++- .../scripts/deployments/oeth-bridged-token.js | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 governance/scripts/deployments/oeth-bridged-token.js diff --git a/governance/.gitignore b/governance/.gitignore index 2e5682418b9..484af673512 100644 --- a/governance/.gitignore +++ b/governance/.gitignore @@ -7,4 +7,6 @@ artifacts zk-artifacts zk-cache artifacts-zk -cache-zk \ No newline at end of file +cache-zk + +!contracts/l2/UDTOptimism.sol \ No newline at end of file diff --git a/governance/scripts/deployments/oeth-bridged-token.js b/governance/scripts/deployments/oeth-bridged-token.js new file mode 100644 index 00000000000..9b284602022 --- /dev/null +++ b/governance/scripts/deployments/oeth-bridged-token.js @@ -0,0 +1,30 @@ +// const { ethers } = require('hardhat') +const { + deployContract, + getNetwork, +} = require('@unlock-protocol/hardhat-helpers') + +async function main({ + bridge = '0x4200000000000000000000000000000000000007', + remoteToken = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98', // '0x90DE74265a416e1393A450752175AED98fe11517', + tokenName = 'Unlock Discount Token', + tokenSymbol = 'UDT', +} = {}) { + const { name, id } = await getNetwork() + // copyAndBuildContractsAtVersion, + + console.log( + `Deployin ${tokenName} (${tokenSymbol}) bridge contract on ${name} (${id})` + ) + + const deployArgs = [bridge, remoteToken, tokenName, tokenSymbol] + await deployContract('UDTOptimism', deployArgs) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + // eslint-disable-next-line no-console + console.error(error) + process.exit(1) + }) From a4c3b514d767e683e5fb602173d2d404810f73db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Tue, 26 Mar 2024 12:50:37 +0100 Subject: [PATCH 03/33] add fixes --- governance/contracts/l2/UDTOptimism.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/governance/contracts/l2/UDTOptimism.sol b/governance/contracts/l2/UDTOptimism.sol index 5a5f5057f70..3cc2b985168 100644 --- a/governance/contracts/l2/UDTOptimism.sol +++ b/governance/contracts/l2/UDTOptimism.sol @@ -64,12 +64,24 @@ contract UDTOptimism is IOptimismMintableERC20, ERC20 { return REMOTE_TOKEN; } + /// @custom:fix + /// @notice Getter for REMOTE_TOKEN required by @eth-optimism/sdk + function l1Token() public view returns (address) { + return REMOTE_TOKEN; + } + /// @custom:legacy /// @notice Legacy getter for BRIDGE. function bridge() public view returns (address) { return BRIDGE; } + /// @custom:fix + /// @notice Getter for REMOTE_TOKEN required by @eth-optimism/sdk + function l2Bridge() public view returns (address) { + return BRIDGE; + } + /// @notice ERC165 interface check function. /// @param _interfaceId Interface ID to check. /// @return Whether or not the interface is supported by this contract. From 9cab5bdddc74746cd0d4893e189301592e3e25cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Tue, 26 Mar 2024 12:51:03 +0100 Subject: [PATCH 04/33] test deploymeny manifest --- governance/.openzeppelin/sepolia.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/governance/.openzeppelin/sepolia.json b/governance/.openzeppelin/sepolia.json index 21640906b54..f2e88140d52 100644 --- a/governance/.openzeppelin/sepolia.json +++ b/governance/.openzeppelin/sepolia.json @@ -19,6 +19,16 @@ "address": "0x447B1492C5038203f1927eB2a374F5Fcdc25999d", "txHash": "0xa552ab865e287870a8c75bca97243b7cdc1a73a60e3221c6198ffaec2cd155fa", "kind": "transparent" + }, + { + "address": "0xAF7B2b27274fa132728a26f611aAF589AB6d4f31", + "txHash": "0x73f52631491158978dee1c0d2ba69b3136d1d76b73b5b69eaadc59e0ed233bca", + "kind": "transparent" + }, + { + "address": "0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98", + "txHash": "0xed5394f06743a9998e02ec842e0d0f1a7a18ca91a3ea307a9cd2c328d213ecce", + "kind": "transparent" } ], "impls": { @@ -1018,4 +1028,4 @@ } } } -} \ No newline at end of file +} From 16fa5896bdccf0cbafa6510a21aed43fd3fece32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Tue, 26 Mar 2024 12:51:34 +0100 Subject: [PATCH 05/33] pass signer to getErc20 helper --- packages/hardhat-helpers/src/fork.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/hardhat-helpers/src/fork.js b/packages/hardhat-helpers/src/fork.js index c6d925491a8..c65d27763ce 100644 --- a/packages/hardhat-helpers/src/fork.js +++ b/packages/hardhat-helpers/src/fork.js @@ -222,12 +222,14 @@ const getDelegates = async () => { return await Promise.all(delegates.map((delegate) => impersonate(delegate))) } -const getERC20Contract = async (tokenAddress) => { +const getERC20Contract = async (tokenAddress, signer) => { const { ethers } = require('hardhat') const { nativeCurrency: { wrapped }, } = await getNetwork() - const [signer] = await ethers.getSigners() + if (!signer) { + ;[signer] = await ethers.getSigners() + } return tokenAddress === wrapped ? await ethers.getContractAt(WETH_ABI, wrapped, signer) : await ethers.getContractAt(ERC20_ABI, tokenAddress, signer) From eddd6f903c946317667ddd10ba0198a2ff7da1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Tue, 26 Mar 2024 12:52:01 +0100 Subject: [PATCH 06/33] add scripts --- .../scripts/deployments/oeth-bridged-token.js | 13 ++- smart-contracts/scripts/l2tokens.js | 102 ++++++++++++++++++ 2 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 smart-contracts/scripts/l2tokens.js diff --git a/governance/scripts/deployments/oeth-bridged-token.js b/governance/scripts/deployments/oeth-bridged-token.js index 9b284602022..e204e29d0a8 100644 --- a/governance/scripts/deployments/oeth-bridged-token.js +++ b/governance/scripts/deployments/oeth-bridged-token.js @@ -1,24 +1,23 @@ -// const { ethers } = require('hardhat') const { deployContract, getNetwork, } = require('@unlock-protocol/hardhat-helpers') async function main({ - bridge = '0x4200000000000000000000000000000000000007', - remoteToken = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98', // '0x90DE74265a416e1393A450752175AED98fe11517', - tokenName = 'Unlock Discount Token', - tokenSymbol = 'UDT', + bridge = '0x4200000000000000000000000000000000000010', + remoteToken = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98', // UDT on Sepolia + tokenName = 'UDT (Bridged)', + tokenSymbol = 'UDT.b', } = {}) { const { name, id } = await getNetwork() // copyAndBuildContractsAtVersion, console.log( - `Deployin ${tokenName} (${tokenSymbol}) bridge contract on ${name} (${id})` + `Deploying ${tokenName} (${tokenSymbol}) bridge contract on ${name} (${id})` ) const deployArgs = [bridge, remoteToken, tokenName, tokenSymbol] - await deployContract('UDTOptimism', deployArgs) + await deployContract('contracts/l2/UDTOptimism.sol:UDTOptimism', deployArgs) } main() diff --git a/smart-contracts/scripts/l2tokens.js b/smart-contracts/scripts/l2tokens.js new file mode 100644 index 00000000000..42c4da8d714 --- /dev/null +++ b/smart-contracts/scripts/l2tokens.js @@ -0,0 +1,102 @@ +const { + getERC20Contract, + getNetwork, +} = require('@unlock-protocol/hardhat-helpers') +const { ethers } = require('hardhat') +const optimism = require('@eth-optimism/sdk') + +const L1_UDT_SEPOLIA = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98' +const L2_UDT_OP_SEPOLIA = '0xfa7AC1c24339f629826C419eC95961Df58563438' + +const OP_SEPOLIA_NETWORK = { + name: 'Op Sepolia', + id: 11155420, + provider: 'https://sepolia.optimism.io', +} + +async function main({ + l1TokenAddress = L1_UDT_SEPOLIA, + l2TokenAddress = L2_UDT_OP_SEPOLIA, + l1ChainId = 11155111, // Sepolia + l2ChainId = OP_SEPOLIA_NETWORK.id, // 10 for OP Mainnet + amount = 1000000000000000000n, // default to 1 +} = {}) { + const { DEPLOYER_PRIVATE_KEY } = process.env + + const [l1, l2] = await Promise.all([ + await getNetwork(l1ChainId), + l2ChainId !== 11155420 ? await getNetwork(l2ChainId) : OP_SEPOLIA_NETWORK, + ]) + + console.log( + `Bridging tokens from L1 ${l1.name} (${l1.id}) to L2 ${l2.name} (${l2.id})...` + ) + + // Create the RPC providers and wallets + const l1Provider = new ethers.providers.StaticJsonRpcProvider(l1.provider) + const l2Provider = new ethers.providers.StaticJsonRpcProvider(l2.provider) + const l1Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l1Provider) + const l2Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l2Provider) + + // tokens + const l1Token = await getERC20Contract(l1TokenAddress, l1Wallet) + const l2Token = await getERC20Contract(l2TokenAddress, l2Wallet) + + console.log( + `Amount: ${ethers.utils.formatUnits( + amount + )} ${await l1Token.symbol()} to ${await l2Token.symbol()} on L2...` + ) + + const showBalances = async () => { + console.log( + `Balance before + - l1: ${(await l1Token.balanceOf(l1Wallet.address)).toString()} + - l2: ${(await l2Token.balanceOf(l2Wallet.address)).toString()}` + ) + } + + // log balances + await showBalances() + + // sdk init + const messenger = new optimism.CrossChainMessenger({ + l1ChainId, + l2ChainId, + l1SignerOrProvider: l1Wallet, + l2SignerOrProvider: l2Wallet, + }) + + console.log(`Approving tokens...`) + const txApprove = await messenger.approveERC20( + l1TokenAddress, + l2TokenAddress, + amount + ) + await txApprove.wait() + + console.log(`Deposit tokens...`) + const txDeposit = await messenger.depositERC20(l1Token, l2Token, amount) + await txDeposit.wait() + + // wait for deposit to be ready + console.log(`Wait for the deposit to be ready...`) + await messenger.waitForMessageStatus( + txDeposit.hash, + optimism.MessageStatus.RELAYED + ) + + // check balances after operations + console.log(`Deposit done.`) + await showBalances() +} + +// execute as standalone +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) +} From 022350b2c2e8d5a4136a310dade66670c1b0119f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Tue, 26 Mar 2024 12:52:40 +0100 Subject: [PATCH 07/33] add optimsim sdk dep --- smart-contracts/package.json | 1 + yarn.lock | 138 ++++++++++++++++++++++++++++++++--- 2 files changed, 130 insertions(+), 9 deletions(-) diff --git a/smart-contracts/package.json b/smart-contracts/package.json index 4078c209ade..08148f7c811 100644 --- a/smart-contracts/package.json +++ b/smart-contracts/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@connext/interfaces": "2.0.5", + "@eth-optimism/sdk": "3.2.3", "@nomiclabs/hardhat-ethers": "2.2.3", "@nomiclabs/hardhat-etherscan": "3.1.8", "@nomiclabs/hardhat-waffle": "2.0.6", diff --git a/yarn.lock b/yarn.lock index dfe2898f012..527aca184d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7513,6 +7513,89 @@ __metadata: languageName: node linkType: hard +"@eth-optimism/contracts-bedrock@npm:0.17.1": + version: 0.17.1 + resolution: "@eth-optimism/contracts-bedrock@npm:0.17.1" + checksum: 10/341e19a32ed451209c7111dc0dbf7e8715d8e5b536f0e7e8e879a7f2ccdf0d3fbdc76abebe20484c0bc86f3cc8eda53c362a7b8c8b10b4bbabf8415fd4520234 + languageName: node + linkType: hard + +"@eth-optimism/contracts@npm:0.6.0": + version: 0.6.0 + resolution: "@eth-optimism/contracts@npm:0.6.0" + dependencies: + "@eth-optimism/core-utils": "npm:0.12.0" + "@ethersproject/abstract-provider": "npm:^5.7.0" + "@ethersproject/abstract-signer": "npm:^5.7.0" + peerDependencies: + ethers: ^5 + checksum: 10/dd1fa303ca39125d45fa71a2be0fe773971a986d0694ba98075b9b93ee3c0c71764fd061f1094f82c36d5aa167f5340ec92ef1ec45d901cb69ace086327c0cf2 + languageName: node + linkType: hard + +"@eth-optimism/core-utils@npm:0.12.0": + version: 0.12.0 + resolution: "@eth-optimism/core-utils@npm:0.12.0" + dependencies: + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/abstract-provider": "npm:^5.7.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/bytes": "npm:^5.7.0" + "@ethersproject/constants": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/hash": "npm:^5.7.0" + "@ethersproject/keccak256": "npm:^5.7.0" + "@ethersproject/properties": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@ethersproject/rlp": "npm:^5.7.0" + "@ethersproject/transactions": "npm:^5.7.0" + "@ethersproject/web": "npm:^5.7.0" + bufio: "npm:^1.0.7" + chai: "npm:^4.3.4" + checksum: 10/a7ea17a8b529b2c86b00ef19fa562c2b792d7e8a4071defea4d8a8b82a101105a3ab6dc86361118e17bf9b4784b4eca9c1e937c8b1e7294a1a850f97b5a73a10 + languageName: node + linkType: hard + +"@eth-optimism/core-utils@npm:0.13.1": + version: 0.13.1 + resolution: "@eth-optimism/core-utils@npm:0.13.1" + dependencies: + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/abstract-provider": "npm:^5.7.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/bytes": "npm:^5.7.0" + "@ethersproject/constants": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/keccak256": "npm:^5.7.0" + "@ethersproject/properties": "npm:^5.7.0" + "@ethersproject/rlp": "npm:^5.7.0" + "@ethersproject/web": "npm:^5.7.1" + chai: "npm:^4.3.9" + ethers: "npm:^5.7.2" + node-fetch: "npm:^2.6.7" + checksum: 10/7d9a3b94d05c3becce24562003032d6d2ddc4396e6420152ee3ad287a614ca513c53d43ecaeba5e238abb8bd85c352a42854a0f949df19cfb0219fc441e2da09 + languageName: node + linkType: hard + +"@eth-optimism/sdk@npm:3.2.3": + version: 3.2.3 + resolution: "@eth-optimism/sdk@npm:3.2.3" + dependencies: + "@eth-optimism/contracts": "npm:0.6.0" + "@eth-optimism/contracts-bedrock": "npm:0.17.1" + "@eth-optimism/core-utils": "npm:0.13.1" + lodash: "npm:^4.17.21" + merkletreejs: "npm:^0.3.11" + rlp: "npm:^2.2.7" + semver: "npm:^7.6.0" + peerDependencies: + ethers: ^5 + checksum: 10/c2ae9e1a34890b5c3335bf08e5c449f3fa75acdff028bff4b6a56cad33ee27d04396bb5d0d83c8631f5a4a60a79dd8956a32cd32a705d5b6ed97194e21d72348 + languageName: node + linkType: hard + "@ethereumjs/common@npm:2.6.5, @ethereumjs/common@npm:^2.6.4": version: 2.6.5 resolution: "@ethereumjs/common@npm:2.6.5" @@ -7797,7 +7880,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.7.1, @ethersproject/providers@npm:^5.7.2": +"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.7.0, @ethersproject/providers@npm:^5.7.1, @ethersproject/providers@npm:^5.7.2": version: 5.7.2 resolution: "@ethersproject/providers@npm:5.7.2" dependencies: @@ -7946,7 +8029,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": +"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0, @ethersproject/web@npm:^5.7.1": version: 5.7.1 resolution: "@ethersproject/web@npm:5.7.1" dependencies: @@ -19883,6 +19966,7 @@ __metadata: version: 0.0.0-use.local resolution: "@unlock-protocol/governance@workspace:governance" dependencies: + "@eth-optimism/sdk": "npm:3.2.3" "@matterlabs/hardhat-zksync-deploy": "npm:1.1.2" "@matterlabs/hardhat-zksync-solc": "npm:1.1.0" "@matterlabs/hardhat-zksync-upgradable": "npm:1.2.4" @@ -19903,6 +19987,7 @@ __metadata: "@unlock-protocol/networks": "workspace:./packages/networks" eslint: "npm:8.54.0" ethers: "npm:6.10.0" + ethers5: "npm:ethers@5" fs-extra: "npm:11.2.0" hardhat: "npm:2.20.1" solhint: "npm:4.1.1" @@ -20115,6 +20200,7 @@ __metadata: resolution: "@unlock-protocol/smart-contracts@workspace:smart-contracts" dependencies: "@connext/interfaces": "npm:2.0.5" + "@eth-optimism/sdk": "npm:3.2.3" "@nomiclabs/hardhat-ethers": "npm:2.2.3" "@nomiclabs/hardhat-etherscan": "npm:3.1.8" "@nomiclabs/hardhat-waffle": "npm:2.0.6" @@ -24021,7 +24107,7 @@ __metadata: languageName: node linkType: hard -"bignumber.js@npm:^9.0.2": +"bignumber.js@npm:^9.0.1, bignumber.js@npm:^9.0.2": version: 9.1.2 resolution: "bignumber.js@npm:9.1.2" checksum: 10/d89b8800a987225d2c00dcbf8a69dc08e92aa0880157c851c287b307d31ceb2fc2acb0c62c3e3a3d42b6c5fcae9b004035f13eb4386e56d529d7edac18d5c9d8 @@ -24691,6 +24777,13 @@ __metadata: languageName: node linkType: hard +"buffer-reverse@npm:^1.0.1": + version: 1.0.1 + resolution: "buffer-reverse@npm:1.0.1" + checksum: 10/e350872a89b17af0a7e1bd7a73239a535164f3f010b0800add44f2e52bd0511548dc5b96c20309effba969868c385023d2d02a0add6155f6a76da7b3073b77bd + languageName: node + linkType: hard + "buffer-to-arraybuffer@npm:^0.0.5": version: 0.0.5 resolution: "buffer-to-arraybuffer@npm:0.0.5" @@ -24763,6 +24856,13 @@ __metadata: languageName: node linkType: hard +"bufio@npm:^1.0.7": + version: 1.2.1 + resolution: "bufio@npm:1.2.1" + checksum: 10/c8920ee0d765eb97d218643705346c3360ae6227414df7b3f345932531b85d18fe385d0740e1bcda6225fa082a179910679f444a6de5aec7aecd176a01e40573 + languageName: node + linkType: hard + "buildcheck@npm:~0.0.6": version: 0.0.6 resolution: "buildcheck@npm:0.0.6" @@ -25320,7 +25420,7 @@ __metadata: languageName: node linkType: hard -"chai@npm:^4.3.10, chai@npm:^4.3.6": +"chai@npm:^4.3.10, chai@npm:^4.3.4, chai@npm:^4.3.6, chai@npm:^4.3.9": version: 4.4.1 resolution: "chai@npm:4.4.1" dependencies: @@ -27150,7 +27250,7 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:^4.1.1": +"crypto-js@npm:^4.1.1, crypto-js@npm:^4.2.0": version: 4.2.0 resolution: "crypto-js@npm:4.2.0" checksum: 10/c7bcc56a6e01c3c397e95aa4a74e4241321f04677f9a618a8f48a63b5781617248afb9adb0629824792e7ec20ca0d4241a49b6b2938ae6f973ec4efc5c53c924 @@ -30939,7 +31039,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:5.7.2, ethers@npm:^5.3.1, ethers@npm:^5.5.4, ethers@npm:^5.6.1, ethers@npm:^5.7.0, ethers@npm:^5.7.1, ethers@npm:^5.7.2": +"ethers5@npm:ethers@5, ethers@npm:5.7.2, ethers@npm:^5.3.1, ethers@npm:^5.5.4, ethers@npm:^5.6.1, ethers@npm:^5.7.0, ethers@npm:^5.7.1, ethers@npm:^5.7.2": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: @@ -39625,6 +39725,19 @@ __metadata: languageName: node linkType: hard +"merkletreejs@npm:^0.3.11": + version: 0.3.11 + resolution: "merkletreejs@npm:0.3.11" + dependencies: + bignumber.js: "npm:^9.0.1" + buffer-reverse: "npm:^1.0.1" + crypto-js: "npm:^4.2.0" + treeify: "npm:^1.1.0" + web3-utils: "npm:^1.3.4" + checksum: 10/a93520ef768648d1e4ebd175182bd3d304270b8eb0700df5be99395fb3ad8805407bdaf3231d13b1649e87de799aa06d71c616db047ad039025764eb23b02244 + languageName: node + linkType: hard + "meros@npm:^1.2.1": version: 1.3.0 resolution: "meros@npm:1.3.0" @@ -47850,7 +47963,7 @@ __metadata: languageName: node linkType: hard -"rlp@npm:^2.0.0, rlp@npm:^2.2.3, rlp@npm:^2.2.4": +"rlp@npm:^2.0.0, rlp@npm:^2.2.3, rlp@npm:^2.2.4, rlp@npm:^2.2.7": version: 2.2.7 resolution: "rlp@npm:2.2.7" dependencies: @@ -48551,7 +48664,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.1, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4": +"semver@npm:^7.0.0, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.1, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": version: 7.6.0 resolution: "semver@npm:7.6.0" dependencies: @@ -51812,6 +51925,13 @@ __metadata: languageName: node linkType: hard +"treeify@npm:^1.1.0": + version: 1.1.0 + resolution: "treeify@npm:1.1.0" + checksum: 10/5241976a751168fb9894a12d031299f1f6337b7f2cbd3eff22ee86e6777620352a69a1cab0d4709251317ff307eeda0dc45918850974fc44f4c7fc50e623b990 + languageName: node + linkType: hard + "trim-lines@npm:^3.0.0": version: 3.0.1 resolution: "trim-lines@npm:3.0.1" @@ -54818,7 +54938,7 @@ __metadata: languageName: node linkType: hard -"web3-utils@npm:1.10.4, web3-utils@npm:^1.10.3, web3-utils@npm:^1.3.6, web3-utils@npm:^1.8.1": +"web3-utils@npm:1.10.4, web3-utils@npm:^1.10.3, web3-utils@npm:^1.3.4, web3-utils@npm:^1.3.6, web3-utils@npm:^1.8.1": version: 1.10.4 resolution: "web3-utils@npm:1.10.4" dependencies: From 335476e6f332b63bc68aaf9ce83f1ebcb2497800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Tue, 26 Mar 2024 12:54:05 +0100 Subject: [PATCH 08/33] better logs --- smart-contracts/scripts/l2tokens.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/smart-contracts/scripts/l2tokens.js b/smart-contracts/scripts/l2tokens.js index 42c4da8d714..f6be4b5553a 100644 --- a/smart-contracts/scripts/l2tokens.js +++ b/smart-contracts/scripts/l2tokens.js @@ -29,7 +29,9 @@ async function main({ ]) console.log( - `Bridging tokens from L1 ${l1.name} (${l1.id}) to L2 ${l2.name} (${l2.id})...` + `Bridging tokens from L1 ${l1.name} (${l1.id}) to L2 ${l2.name} (${l2.id})... + - L1: ${l1TokenAddress} + - L2: ${l2TokenAddress}` ) // Create the RPC providers and wallets From cb40ace1b72d2e7a226c29bfcf148966ce1778be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Tue, 26 Mar 2024 13:01:58 +0100 Subject: [PATCH 09/33] balances log --- smart-contracts/scripts/l2tokens.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/smart-contracts/scripts/l2tokens.js b/smart-contracts/scripts/l2tokens.js index f6be4b5553a..50905ee050e 100644 --- a/smart-contracts/scripts/l2tokens.js +++ b/smart-contracts/scripts/l2tokens.js @@ -50,16 +50,16 @@ async function main({ )} ${await l1Token.symbol()} to ${await l2Token.symbol()} on L2...` ) - const showBalances = async () => { + const showBalances = async (text = 'Balance') => { console.log( - `Balance before + `${text} - l1: ${(await l1Token.balanceOf(l1Wallet.address)).toString()} - l2: ${(await l2Token.balanceOf(l2Wallet.address)).toString()}` ) } // log balances - await showBalances() + await showBalances('Balance before') // sdk init const messenger = new optimism.CrossChainMessenger({ @@ -90,7 +90,7 @@ async function main({ // check balances after operations console.log(`Deposit done.`) - await showBalances() + await showBalances('Balance after') } // execute as standalone From 89a74f54454bbd615434ea4b7dc55fa358d6e361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 27 Mar 2024 14:04:47 +0100 Subject: [PATCH 10/33] scrpt to deploy bridged token on OP --- smart-contracts/scripts/deployErc20onOP.js | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 smart-contracts/scripts/deployErc20onOP.js diff --git a/smart-contracts/scripts/deployErc20onOP.js b/smart-contracts/scripts/deployErc20onOP.js new file mode 100644 index 00000000000..85d605f28a2 --- /dev/null +++ b/smart-contracts/scripts/deployErc20onOP.js @@ -0,0 +1,77 @@ +const { getNetwork } = require('@unlock-protocol/hardhat-helpers') +const { ethers } = require('hardhat') +const fs = require('fs-extra') + +const L1_TEST_TOKEN = '0x5589BB8228C07c4e15558875fAf2B859f678d129' + +const OP_SEPOLIA_NETWORK = { + name: 'Op Sepolia', + id: 11155420, + provider: 'https://sepolia.optimism.io', +} + +async function main({ + l1TokenAddress = L1_TEST_TOKEN, + tokenSymbol = 'UDT.e', + tokenName = 'UnlockDiscountToken (bridged)', + l2ChainId = OP_SEPOLIA_NETWORK.id, // 10 for OP Mainnet +} = {}) { + const { DEPLOYER_PRIVATE_KEY } = process.env + + // get l2 network info + const l2 = OP_SEPOLIA_NETWORK.id + ? OP_SEPOLIA_NETWORK + : await getNetwork(l2ChainId) + + console.log( + `Deploying bridged token from L1 ${l1TokenAddress} to L2 ${l2.name} (${l2.id})... + - token: ${l1TokenAddress} ` + ) + + // Create the RPC providers and wallets + const l2Provider = new ethers.providers.StaticJsonRpcProvider(l2.provider) + const l2Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l2Provider) + + // read ABI from node_modules + const { abi: factoryAbi } = JSON.parse( + fs + .readFileSync( + '../node_modules/@eth-optimism/contracts-bedrock/deployments/mainnet/OptimismMintableERC20Factory.json' + ) + .toString() + .replace(/\n/g, '') + ) + + // get factory contract instance + const factoryAddress = '0x4200000000000000000000000000000000000012' + const factory = await ethers.getContractAt( + factoryAbi, + factoryAddress, + l2Wallet + ) + + // create bridged token + const tx = await factory.createOptimismMintableERC20( + l1TokenAddress, + tokenName, + tokenSymbol + ) + + // fetch info from tx + const { events, transactionHash } = await tx.wait() + const { + args: { localToken }, + } = events.find(({ event }) => event === 'OptimismMintableERC20Created') + + console.log(`Bridged token deployed at: ${localToken} (${transactionHash})`) +} + +// execute as standalone +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) +} From 6901fb35508c914955421b9c2bb1db546bf0f76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 27 Mar 2024 14:06:21 +0100 Subject: [PATCH 11/33] delete custom bridge contract --- governance/contracts/l2/UDTOptimism.sol | 115 ------------------ .../scripts/deployments/oeth-bridged-token.js | 29 ----- 2 files changed, 144 deletions(-) delete mode 100644 governance/contracts/l2/UDTOptimism.sol delete mode 100644 governance/scripts/deployments/oeth-bridged-token.js diff --git a/governance/contracts/l2/UDTOptimism.sol b/governance/contracts/l2/UDTOptimism.sol deleted file mode 100644 index 3cc2b985168..00000000000 --- a/governance/contracts/l2/UDTOptimism.sol +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/// @title IOptimismMintableERC20 -/// @notice This interface is available on the OptimismMintableERC20 contract. -/// We declare it as a separate interface so that it can be used in -/// custom implementations of OptimismMintableERC20. -interface IOptimismMintableERC20 is IERC165 { - function remoteToken() external view returns (address); - - function bridge() external returns (address); - - function mint(address _to, uint256 _amount) external; - - function burn(address _from, uint256 _amount) external; -} - -contract UDTOptimism is IOptimismMintableERC20, ERC20 { - /// @notice Address of the corresponding version of this token on the remote chain. - address public immutable REMOTE_TOKEN; - - /// @notice Address of the StandardBridge on this network. - address public immutable BRIDGE; - - /// @notice Emitted whenever tokens are minted for an account. - /// @param account Address of the account tokens are being minted for. - /// @param amount Amount of tokens minted. - event Mint(address indexed account, uint256 amount); - - /// @notice Emitted whenever tokens are burned from an account. - /// @param account Address of the account tokens are being burned from. - /// @param amount Amount of tokens burned. - event Burn(address indexed account, uint256 amount); - - /// @notice A modifier that only allows the bridge to call. - modifier onlyBridge() { - require( - msg.sender == BRIDGE, - "UnlockDiscountToken: only bridge can mint and burn" - ); - _; - } - - /// @param _bridge Address of the L2 standard bridge. - /// @param _remoteToken Address of the corresponding L1 token. - /// @param _name ERC20 name. - /// @param _symbol ERC20 symbol. - constructor( - address _bridge, - address _remoteToken, - string memory _name, - string memory _symbol - ) ERC20(_name, _symbol) { - REMOTE_TOKEN = _remoteToken; - BRIDGE = _bridge; - } - - /// @custom:legacy - /// @notice Legacy getter for REMOTE_TOKEN. - function remoteToken() public view returns (address) { - return REMOTE_TOKEN; - } - - /// @custom:fix - /// @notice Getter for REMOTE_TOKEN required by @eth-optimism/sdk - function l1Token() public view returns (address) { - return REMOTE_TOKEN; - } - - /// @custom:legacy - /// @notice Legacy getter for BRIDGE. - function bridge() public view returns (address) { - return BRIDGE; - } - - /// @custom:fix - /// @notice Getter for REMOTE_TOKEN required by @eth-optimism/sdk - function l2Bridge() public view returns (address) { - return BRIDGE; - } - - /// @notice ERC165 interface check function. - /// @param _interfaceId Interface ID to check. - /// @return Whether or not the interface is supported by this contract. - function supportsInterface( - bytes4 _interfaceId - ) external pure virtual returns (bool) { - bytes4 iface1 = type(IERC165).interfaceId; - // Interface corresponding to the updated OptimismMintableERC20 (this contract). - bytes4 iface2 = type(IOptimismMintableERC20).interfaceId; - return _interfaceId == iface1 || _interfaceId == iface2; - } - - /// @notice Allows the StandardBridge on this network to mint tokens. - /// @param _to Address to mint tokens to. - /// @param _amount Amount of tokens to mint. - function mint( - address _to, - uint256 _amount - ) external virtual override(IOptimismMintableERC20) onlyBridge { - _mint(_to, _amount); - emit Mint(_to, _amount); - } - - /// @notice Prevents tokens from being withdrawn to L1. - function burn( - address, - uint256 - ) external virtual override(IOptimismMintableERC20) onlyBridge { - revert("UnlockDiscountToken cannot be withdrawn"); - } -} diff --git a/governance/scripts/deployments/oeth-bridged-token.js b/governance/scripts/deployments/oeth-bridged-token.js deleted file mode 100644 index e204e29d0a8..00000000000 --- a/governance/scripts/deployments/oeth-bridged-token.js +++ /dev/null @@ -1,29 +0,0 @@ -const { - deployContract, - getNetwork, -} = require('@unlock-protocol/hardhat-helpers') - -async function main({ - bridge = '0x4200000000000000000000000000000000000010', - remoteToken = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98', // UDT on Sepolia - tokenName = 'UDT (Bridged)', - tokenSymbol = 'UDT.b', -} = {}) { - const { name, id } = await getNetwork() - // copyAndBuildContractsAtVersion, - - console.log( - `Deploying ${tokenName} (${tokenSymbol}) bridge contract on ${name} (${id})` - ) - - const deployArgs = [bridge, remoteToken, tokenName, tokenSymbol] - await deployContract('contracts/l2/UDTOptimism.sol:UDTOptimism', deployArgs) -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - // eslint-disable-next-line no-console - console.error(error) - process.exit(1) - }) From 1f39bfd4d9e1bbbbdf71c59f41efd8cfa1901a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 27 Mar 2024 14:20:00 +0100 Subject: [PATCH 12/33] move deploy script to governance --- .../scripts/deployments/OPBridgedToken.js | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) rename smart-contracts/scripts/deployErc20onOP.js => governance/scripts/deployments/OPBridgedToken.js (69%) diff --git a/smart-contracts/scripts/deployErc20onOP.js b/governance/scripts/deployments/OPBridgedToken.js similarity index 69% rename from smart-contracts/scripts/deployErc20onOP.js rename to governance/scripts/deployments/OPBridgedToken.js index 85d605f28a2..50b83cbb72b 100644 --- a/smart-contracts/scripts/deployErc20onOP.js +++ b/governance/scripts/deployments/OPBridgedToken.js @@ -1,8 +1,10 @@ -const { getNetwork } = require('@unlock-protocol/hardhat-helpers') +const { getNetwork, getEvent } = require('@unlock-protocol/hardhat-helpers') +const { getProvider } = require('../../helpers/multisig') + const { ethers } = require('hardhat') const fs = require('fs-extra') -const L1_TEST_TOKEN = '0x5589BB8228C07c4e15558875fAf2B859f678d129' +const L1_UDT_SEPOLIA = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98' const OP_SEPOLIA_NETWORK = { name: 'Op Sepolia', @@ -11,7 +13,7 @@ const OP_SEPOLIA_NETWORK = { } async function main({ - l1TokenAddress = L1_TEST_TOKEN, + l1TokenAddress = L1_UDT_SEPOLIA, tokenSymbol = 'UDT.e', tokenName = 'UnlockDiscountToken (bridged)', l2ChainId = OP_SEPOLIA_NETWORK.id, // 10 for OP Mainnet @@ -19,9 +21,10 @@ async function main({ const { DEPLOYER_PRIVATE_KEY } = process.env // get l2 network info - const l2 = OP_SEPOLIA_NETWORK.id - ? OP_SEPOLIA_NETWORK - : await getNetwork(l2ChainId) + const l2 = + l2ChainId === OP_SEPOLIA_NETWORK.id + ? OP_SEPOLIA_NETWORK + : await getNetwork(l2ChainId) console.log( `Deploying bridged token from L1 ${l1TokenAddress} to L2 ${l2.name} (${l2.id})... @@ -29,7 +32,10 @@ async function main({ ) // Create the RPC providers and wallets - const l2Provider = new ethers.providers.StaticJsonRpcProvider(l2.provider) + const l2Provider = + l2ChainId === OP_SEPOLIA_NETWORK.id + ? new ethers.JsonRpcProvider(l2.provider) + : await getProvider(l2ChainId) const l2Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l2Provider) // read ABI from node_modules @@ -58,12 +64,13 @@ async function main({ ) // fetch info from tx - const { events, transactionHash } = await tx.wait() + const receipt = await tx.wait() const { args: { localToken }, - } = events.find(({ event }) => event === 'OptimismMintableERC20Created') + hash, + } = await getEvent(receipt, 'OptimismMintableERC20Created') - console.log(`Bridged token deployed at: ${localToken} (${transactionHash})`) + console.log(`Bridged token deployed at: ${localToken} (${hash})`) } // execute as standalone From a66bb92400dfe93ab4e73e66f7911308bee4358c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 27 Mar 2024 14:24:00 +0100 Subject: [PATCH 13/33] remove refs to OP sepolia --- .../scripts/deployments/OPBridgedToken.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/governance/scripts/deployments/OPBridgedToken.js b/governance/scripts/deployments/OPBridgedToken.js index 50b83cbb72b..e8b18b10b9d 100644 --- a/governance/scripts/deployments/OPBridgedToken.js +++ b/governance/scripts/deployments/OPBridgedToken.js @@ -6,25 +6,16 @@ const fs = require('fs-extra') const L1_UDT_SEPOLIA = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98' -const OP_SEPOLIA_NETWORK = { - name: 'Op Sepolia', - id: 11155420, - provider: 'https://sepolia.optimism.io', -} - async function main({ l1TokenAddress = L1_UDT_SEPOLIA, tokenSymbol = 'UDT.e', tokenName = 'UnlockDiscountToken (bridged)', - l2ChainId = OP_SEPOLIA_NETWORK.id, // 10 for OP Mainnet + l2ChainId = 84532, // Base Sepolia } = {}) { const { DEPLOYER_PRIVATE_KEY } = process.env // get l2 network info - const l2 = - l2ChainId === OP_SEPOLIA_NETWORK.id - ? OP_SEPOLIA_NETWORK - : await getNetwork(l2ChainId) + const l2 = await getNetwork(l2ChainId) console.log( `Deploying bridged token from L1 ${l1TokenAddress} to L2 ${l2.name} (${l2.id})... @@ -32,10 +23,7 @@ async function main({ ) // Create the RPC providers and wallets - const l2Provider = - l2ChainId === OP_SEPOLIA_NETWORK.id - ? new ethers.JsonRpcProvider(l2.provider) - : await getProvider(l2ChainId) + const l2Provider = await getProvider(l2ChainId) const l2Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l2Provider) // read ABI from node_modules From c8936a1e80c60989310dba465f4a2eea08321ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 10:33:15 +0100 Subject: [PATCH 14/33] cleanup gitignore --- governance/.gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/governance/.gitignore b/governance/.gitignore index 484af673512..2e5682418b9 100644 --- a/governance/.gitignore +++ b/governance/.gitignore @@ -7,6 +7,4 @@ artifacts zk-artifacts zk-cache artifacts-zk -cache-zk - -!contracts/l2/UDTOptimism.sol \ No newline at end of file +cache-zk \ No newline at end of file From 63a0b6d7fde06b8624916026b450b27ffebeb4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 10:42:59 +0100 Subject: [PATCH 15/33] cleanup and use values from networks package --- .../scripts/deployments/OPBridgedToken.js | 25 ++++++++++++--- smart-contracts/scripts/l2tokens.js | 31 +++++++++++-------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/governance/scripts/deployments/OPBridgedToken.js b/governance/scripts/deployments/OPBridgedToken.js index e8b18b10b9d..6e7048c8c23 100644 --- a/governance/scripts/deployments/OPBridgedToken.js +++ b/governance/scripts/deployments/OPBridgedToken.js @@ -1,24 +1,40 @@ +/** + * Deploy a bridged ERC20 UDT token contract on Optimism networks + * + * Please edit the chain ids constant below to use + */ const { getNetwork, getEvent } = require('@unlock-protocol/hardhat-helpers') const { getProvider } = require('../../helpers/multisig') const { ethers } = require('hardhat') const fs = require('fs-extra') -const L1_UDT_SEPOLIA = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98' +// edit default values +const L1_CHAIN_ID = 11155111 // default to Sepolia +const l2_CHAIN_ID = 84532 // default to Base Sepolia async function main({ - l1TokenAddress = L1_UDT_SEPOLIA, tokenSymbol = 'UDT.e', tokenName = 'UnlockDiscountToken (bridged)', - l2ChainId = 84532, // Base Sepolia + factoryAddress = '0x4200000000000000000000000000000000000012', // bridged token factory address + l1ChainId = L1_CHAIN_ID, + l2ChainId = l2_CHAIN_ID, } = {}) { const { DEPLOYER_PRIVATE_KEY } = process.env + const { + name: l1Name, + unlockDaoToken: { address: l1TokenAddress }, + } = await getNetwork(l1ChainId) + + if (!l1TokenAddress) { + throw new Error(`Missing UDT on L1 ${l1Name}`) + } // get l2 network info const l2 = await getNetwork(l2ChainId) console.log( - `Deploying bridged token from L1 ${l1TokenAddress} to L2 ${l2.name} (${l2.id})... + `Deploying bridged token from L1 (${l1Name}) ${l1TokenAddress} to L2 ${l2.name} (${l2.id})... - token: ${l1TokenAddress} ` ) @@ -37,7 +53,6 @@ async function main({ ) // get factory contract instance - const factoryAddress = '0x4200000000000000000000000000000000000012' const factory = await ethers.getContractAt( factoryAbi, factoryAddress, diff --git a/smart-contracts/scripts/l2tokens.js b/smart-contracts/scripts/l2tokens.js index 50905ee050e..a3033484809 100644 --- a/smart-contracts/scripts/l2tokens.js +++ b/smart-contracts/scripts/l2tokens.js @@ -1,3 +1,12 @@ +/** + * Bridge tokens to Optimism networks + * + * TODO: move to governance foldder + * NB: this scripts belong to `governance` folder but lives here as the Optimsim sdk + * requires ethers v5 ( in use in this workspace), while the governance workspace + * uses v6. + */ + const { getERC20Contract, getNetwork, @@ -5,29 +14,25 @@ const { const { ethers } = require('hardhat') const optimism = require('@eth-optimism/sdk') -const L1_UDT_SEPOLIA = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98' -const L2_UDT_OP_SEPOLIA = '0xfa7AC1c24339f629826C419eC95961Df58563438' - -const OP_SEPOLIA_NETWORK = { - name: 'Op Sepolia', - id: 11155420, - provider: 'https://sepolia.optimism.io', -} +// edit default values +const L1_CHAIN_ID = 11155111 // default to Sepolia +const l2_CHAIN_ID = 84532 // default to Base Sepolia async function main({ - l1TokenAddress = L1_UDT_SEPOLIA, - l2TokenAddress = L2_UDT_OP_SEPOLIA, - l1ChainId = 11155111, // Sepolia - l2ChainId = OP_SEPOLIA_NETWORK.id, // 10 for OP Mainnet + l1ChainId = L1_CHAIN_ID, + l2ChainId = l2_CHAIN_ID, amount = 1000000000000000000n, // default to 1 } = {}) { const { DEPLOYER_PRIVATE_KEY } = process.env const [l1, l2] = await Promise.all([ await getNetwork(l1ChainId), - l2ChainId !== 11155420 ? await getNetwork(l2ChainId) : OP_SEPOLIA_NETWORK, + await getNetwork(l2ChainId), ]) + const l1TokenAddress = l1.unlockDaoToken.address + const l2TokenAddress = l2.unlockDaoToken.address + console.log( `Bridging tokens from L1 ${l1.name} (${l1.id}) to L2 ${l2.name} (${l2.id})... - L1: ${l1TokenAddress} From 4cf77f3446e65db032557ce14a8a401455fcaebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 11:52:31 +0100 Subject: [PATCH 16/33] arb bridge contract --- smart-contracts/package.json | 1 + smart-contracts/scripts/ARBBridgedToken.js | 123 +++++++++++++++++++++ yarn.lock | 33 +++++- 3 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 smart-contracts/scripts/ARBBridgedToken.js diff --git a/smart-contracts/package.json b/smart-contracts/package.json index 08148f7c811..34069d17c46 100644 --- a/smart-contracts/package.json +++ b/smart-contracts/package.json @@ -8,6 +8,7 @@ "test": "test" }, "dependencies": { + "@arbitrum/sdk": "3.3.3", "@connext/interfaces": "2.0.5", "@eth-optimism/sdk": "3.2.3", "@nomiclabs/hardhat-ethers": "2.2.3", diff --git a/smart-contracts/scripts/ARBBridgedToken.js b/smart-contracts/scripts/ARBBridgedToken.js new file mode 100644 index 00000000000..0f4a4d8621c --- /dev/null +++ b/smart-contracts/scripts/ARBBridgedToken.js @@ -0,0 +1,123 @@ +/** + * Deploy a bridged ERC20 UDT token contract on Arbitrum network + * + * Please edit the chain ids constant below to use + * TODO: move to `governance` workspace - Arbitrum SDK requires ethers@5 + */ +const { getNetwork } = require('@unlock-protocol/hardhat-helpers') + +const { + getL2Network, + Erc20Bridger, + L1ToL2MessageStatus, +} = require('@arbitrum/sdk') +const { ethers } = require('hardhat') + +const L1_CHAIN_ID = 11155111 // default to Sepolia +const l2_CHAIN_ID = 421614 // default to ARB Sepolia +const L1_UDT_SEPOLIA = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98' + +async function main({ + // tokenSymbol = 'UDT.e', + // tokenName = 'UnlockDiscountToken (bridged)', + l1ChainId = L1_CHAIN_ID, + l2ChainId = l2_CHAIN_ID, + amount = 1000000000000000000n, // default to 1 +} = {}) { + const { DEPLOYER_PRIVATE_KEY } = process.env + + // unlock networks info + const [l1, l2] = await Promise.all([ + await getNetwork(l1ChainId), + await getNetwork(l2ChainId), + ]) + + const l1TokenAddress = L1_UDT_SEPOLIA //l1.unlockDaoToken.address + + console.log( + `Bridging tokens from L1 ${l1.name} (${l1.id}) to L2 ${l2.name} (${l2.id})... + - UDT L1: ${l1TokenAddress}` + ) + + // get wallets and providers + const l1Provider = new ethers.providers.StaticJsonRpcProvider(l1.provider) + const l1Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l1Provider) + // const l2Provider = await getProvider(l2ChainId) + const l2Provider = new ethers.providers.StaticJsonRpcProvider( + 'https://sepolia-rollup.arbitrum.io/rpc' + ) + + // token contract instance + const l1Token = await ethers.getContractAt('IERC20', l1TokenAddress, l1Wallet) + + // use arb sdk + const l2Network = await getL2Network(l2Provider) + const erc20Bridger = new Erc20Bridger(l2Network) + + // get arb gateway address + const expectedL1GatewayAddress = await erc20Bridger.getL1GatewayAddress( + l1TokenAddress, + l1Provider + ) + console.log(`Using ARB Gateway at: ${expectedL1GatewayAddress}`) + console.log(`Balances: + - L1 Wallet: ${await l1Token.balanceOf(l1Wallet.address)} + - L1 Gateway / L2: ${await l1Token.balanceOf(expectedL1GatewayAddress)}`) + + console.log('Approving the Bridge to spend token...') + const approveTx = await erc20Bridger.approveToken({ + l1Signer: l1Wallet, + erc20L1Address: l1TokenAddress, + }) + const approveRec = await approveTx.wait() + console.log( + `You successfully allowed the Arbitrum Bridge to spend UDT ${approveRec.transactionHash}` + ) + + console.log(`Depositing ${amount} to L2 via the Gateway bridge contract...`) + const depositTx = await erc20Bridger.deposit({ + amount, + erc20L1Address: l1TokenAddress, + l1Signer: l1Wallet, + l2Provider: l2Provider, + }) + + // Now we wait for L1 and L2 side of transactions to be confirmed + console.log( + `Deposit initiated: waiting for L2 retryable (takes 10-15 minutes; current time: ${new Date().toTimeString()}) ` + ) + const depositRec = await depositTx.wait() + const l2Result = await depositRec.waitForL2(l2Provider) + l2Result.complete + ? console.log( + `L2 message successful: status: ${L1ToL2MessageStatus[l2Result.status]}` + ) + : console.log( + `L2 message failed: status ${L1ToL2MessageStatus[l2Result.status]}` + ) + + // now fetch the token address created on l2 + console.log('Get address on L2...') + const l2TokenAddress = await erc20Bridger.getL2ERC20Address( + l1TokenAddress, + l1Provider + ) + console.log(`L2 token contract created at ${l2TokenAddress}`) + const l2Token = erc20Bridger.getL2TokenContract(l2Provider, l2TokenAddress) + + console.log( + `Balances: + - l1: ${(await l1Token.balanceOf(l1Wallet.address)).toString()} + - l2: ${(await l2Token.balanceOf(l1Wallet.address)).toString()}` + ) +} + +// execute as standalone +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) +} diff --git a/yarn.lock b/yarn.lock index 527aca184d1..92f40ac559c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -257,6 +257,19 @@ __metadata: languageName: node linkType: hard +"@arbitrum/sdk@npm:3.3.3": + version: 3.3.3 + resolution: "@arbitrum/sdk@npm:3.3.3" + dependencies: + "@ethersproject/address": "npm:^5.0.8" + "@ethersproject/bignumber": "npm:^5.1.1" + "@ethersproject/bytes": "npm:^5.0.8" + async-mutex: "npm:^0.4.0" + ethers: "npm:^5.1.0" + checksum: 10/cf21a8750191beaa588bbe350853118d6142ac70aebcdb38306ff5144cbbb89a7ac2680b03537f218bdad9eb528cfe85ba97834794bcbe397ce7d817f2a010c8 + languageName: node + linkType: hard + "@ardatan/relay-compiler@npm:12.0.0": version: 12.0.0 resolution: "@ardatan/relay-compiler@npm:12.0.0" @@ -7698,7 +7711,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.0.0, @ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.0.4, @ethersproject/address@npm:^5.7.0": +"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.0.0, @ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.0.4, @ethersproject/address@npm:^5.0.8, @ethersproject/address@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/address@npm:5.7.0" dependencies: @@ -7730,7 +7743,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.0.7, @ethersproject/bignumber@npm:^5.7.0": +"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.0.7, @ethersproject/bignumber@npm:^5.1.1, @ethersproject/bignumber@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bignumber@npm:5.7.0" dependencies: @@ -7741,7 +7754,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.0.4, @ethersproject/bytes@npm:^5.7.0": +"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.0.4, @ethersproject/bytes@npm:^5.0.8, @ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" dependencies: @@ -19966,7 +19979,6 @@ __metadata: version: 0.0.0-use.local resolution: "@unlock-protocol/governance@workspace:governance" dependencies: - "@eth-optimism/sdk": "npm:3.2.3" "@matterlabs/hardhat-zksync-deploy": "npm:1.1.2" "@matterlabs/hardhat-zksync-solc": "npm:1.1.0" "@matterlabs/hardhat-zksync-upgradable": "npm:1.2.4" @@ -19987,7 +19999,6 @@ __metadata: "@unlock-protocol/networks": "workspace:./packages/networks" eslint: "npm:8.54.0" ethers: "npm:6.10.0" - ethers5: "npm:ethers@5" fs-extra: "npm:11.2.0" hardhat: "npm:2.20.1" solhint: "npm:4.1.1" @@ -20199,6 +20210,7 @@ __metadata: version: 0.0.0-use.local resolution: "@unlock-protocol/smart-contracts@workspace:smart-contracts" dependencies: + "@arbitrum/sdk": "npm:3.3.3" "@connext/interfaces": "npm:2.0.5" "@eth-optimism/sdk": "npm:3.2.3" "@nomiclabs/hardhat-ethers": "npm:2.2.3" @@ -23173,6 +23185,15 @@ __metadata: languageName: node linkType: hard +"async-mutex@npm:^0.4.0": + version: 0.4.1 + resolution: "async-mutex@npm:0.4.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/7e9f77b112b8545beb6612493fae4a8d9d1d6c3f24fc22f4d6d05ce96d1e8d326ac3e743a804cc6d7bf24e7ef0267afb65bb127f99b2e433609684b38933ff1c + languageName: node + linkType: hard + "async-retry@npm:^1.3.1, async-retry@npm:^1.3.3": version: 1.3.3 resolution: "async-retry@npm:1.3.3" @@ -31039,7 +31060,7 @@ __metadata: languageName: node linkType: hard -"ethers5@npm:ethers@5, ethers@npm:5.7.2, ethers@npm:^5.3.1, ethers@npm:^5.5.4, ethers@npm:^5.6.1, ethers@npm:^5.7.0, ethers@npm:^5.7.1, ethers@npm:^5.7.2": +"ethers@npm:5.7.2, ethers@npm:^5.1.0, ethers@npm:^5.3.1, ethers@npm:^5.5.4, ethers@npm:^5.6.1, ethers@npm:^5.7.0, ethers@npm:^5.7.1, ethers@npm:^5.7.2": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: From 4ef2a2796bd704760a585b127224dbd209a85687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 11:53:11 +0100 Subject: [PATCH 17/33] move scripts to bridge folder --- smart-contracts/scripts/{ARBBridgedToken.js => bridge/arb.js} | 0 smart-contracts/scripts/{l2tokens.js => bridge/op.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename smart-contracts/scripts/{ARBBridgedToken.js => bridge/arb.js} (100%) rename smart-contracts/scripts/{l2tokens.js => bridge/op.js} (100%) diff --git a/smart-contracts/scripts/ARBBridgedToken.js b/smart-contracts/scripts/bridge/arb.js similarity index 100% rename from smart-contracts/scripts/ARBBridgedToken.js rename to smart-contracts/scripts/bridge/arb.js diff --git a/smart-contracts/scripts/l2tokens.js b/smart-contracts/scripts/bridge/op.js similarity index 100% rename from smart-contracts/scripts/l2tokens.js rename to smart-contracts/scripts/bridge/op.js From e44552e0eb6c935dbc4ea3fee45fe76cb57b5a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 12:54:29 +0100 Subject: [PATCH 18/33] arb use mainnet --- smart-contracts/scripts/bridge/arb.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/smart-contracts/scripts/bridge/arb.js b/smart-contracts/scripts/bridge/arb.js index 0f4a4d8621c..c1dd5bfe14e 100644 --- a/smart-contracts/scripts/bridge/arb.js +++ b/smart-contracts/scripts/bridge/arb.js @@ -13,13 +13,10 @@ const { } = require('@arbitrum/sdk') const { ethers } = require('hardhat') -const L1_CHAIN_ID = 11155111 // default to Sepolia -const l2_CHAIN_ID = 421614 // default to ARB Sepolia -const L1_UDT_SEPOLIA = '0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98' +const L1_CHAIN_ID = 1 // mainnet (Sepolia 11155111) +const l2_CHAIN_ID = 42161 // ARB (ARB Sepolia 421614) async function main({ - // tokenSymbol = 'UDT.e', - // tokenName = 'UnlockDiscountToken (bridged)', l1ChainId = L1_CHAIN_ID, l2ChainId = l2_CHAIN_ID, amount = 1000000000000000000n, // default to 1 @@ -32,7 +29,7 @@ async function main({ await getNetwork(l2ChainId), ]) - const l1TokenAddress = L1_UDT_SEPOLIA //l1.unlockDaoToken.address + const l1TokenAddress = l1.unlockDaoToken.address console.log( `Bridging tokens from L1 ${l1.name} (${l1.id}) to L2 ${l2.name} (${l2.id})... @@ -43,9 +40,7 @@ async function main({ const l1Provider = new ethers.providers.StaticJsonRpcProvider(l1.provider) const l1Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l1Provider) // const l2Provider = await getProvider(l2ChainId) - const l2Provider = new ethers.providers.StaticJsonRpcProvider( - 'https://sepolia-rollup.arbitrum.io/rpc' - ) + const l2Provider = new ethers.providers.StaticJsonRpcProvider(l2.provider) // token contract instance const l1Token = await ethers.getContractAt('IERC20', l1TokenAddress, l1Wallet) @@ -60,10 +55,15 @@ async function main({ l1Provider ) console.log(`Using ARB Gateway at: ${expectedL1GatewayAddress}`) + const balance = await l1Token.balanceOf(l1Wallet.address) console.log(`Balances: - - L1 Wallet: ${await l1Token.balanceOf(l1Wallet.address)} + - L1 Wallet: ${balance.toString()} - L1 Gateway / L2: ${await l1Token.balanceOf(expectedL1GatewayAddress)}`) + if (balance.lt(amount)) { + throw new Error('Insufficient UDT balance on L1. Can not bridge') + } + console.log('Approving the Bridge to spend token...') const approveTx = await erc20Bridger.approveToken({ l1Signer: l1Wallet, From 3cb1c039df65d02a7ebedadd917c30cdb0176de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 13:01:51 +0100 Subject: [PATCH 19/33] check if token already exists --- smart-contracts/scripts/bridge/arb.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/smart-contracts/scripts/bridge/arb.js b/smart-contracts/scripts/bridge/arb.js index c1dd5bfe14e..379020f41e0 100644 --- a/smart-contracts/scripts/bridge/arb.js +++ b/smart-contracts/scripts/bridge/arb.js @@ -19,7 +19,7 @@ const l2_CHAIN_ID = 42161 // ARB (ARB Sepolia 421614) async function main({ l1ChainId = L1_CHAIN_ID, l2ChainId = l2_CHAIN_ID, - amount = 1000000000000000000n, // default to 1 + amount = 100000000000000000n, // default to 0.1 } = {}) { const { DEPLOYER_PRIVATE_KEY } = process.env @@ -48,6 +48,11 @@ async function main({ // use arb sdk const l2Network = await getL2Network(l2Provider) const erc20Bridger = new Erc20Bridger(l2Network) + const l2TokenAddress = await erc20Bridger.getL2ERC20Address( + l1TokenAddress, + l1Provider + ) + console.log(`L2 ERC20 bridged token contract at ${l2TokenAddress}`) // get arb gateway address const expectedL1GatewayAddress = await erc20Bridger.getL1GatewayAddress( @@ -97,12 +102,14 @@ async function main({ ) // now fetch the token address created on l2 - console.log('Get address on L2...') - const l2TokenAddress = await erc20Bridger.getL2ERC20Address( - l1TokenAddress, - l1Provider - ) - console.log(`L2 token contract created at ${l2TokenAddress}`) + if (!l2TokenAddress) { + console.log('Get address on L2...') + const l2TokenAddress = await erc20Bridger.getL2ERC20Address( + l1TokenAddress, + l1Provider + ) + console.log(`L2 token contract created at ${l2TokenAddress}`) + } const l2Token = erc20Bridger.getL2TokenContract(l2Provider, l2TokenAddress) console.log( From f3f49109d77f045ab12ea01fecbf330b95fd0e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 13:05:05 +0100 Subject: [PATCH 20/33] add arbitrum addresses --- packages/networks/src/networks/arbitrum.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/networks/src/networks/arbitrum.ts b/packages/networks/src/networks/arbitrum.ts index 5882d275ca8..ae8caabcf4c 100644 --- a/packages/networks/src/networks/arbitrum.ts +++ b/packages/networks/src/networks/arbitrum.ts @@ -143,6 +143,10 @@ export const arbitrum: NetworkConfig = { universalRouterAddress: '0x4C60051384bd2d3C01bfc845Cf5F4b44bcbE9de5', }, unlockAddress: '0x1FF7e338d5E582138C46044dc238543Ce555C963', + unlockDaoToken: { + address: '0xd5d3aA404D7562d09a848F96a8a8d5D65977bF90', + // mainnetBridge: '0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf', + }, url: 'https://arbitrum.io/', } From 8795f14d2c0c816d9208408c0a42b611f3d7a518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 13:17:01 +0100 Subject: [PATCH 21/33] OP default to mainnet --- governance/helpers/multisig.js | 2 +- governance/scripts/deployments/OPBridgedToken.js | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/governance/helpers/multisig.js b/governance/helpers/multisig.js index 439b91c7901..166c054301c 100644 --- a/governance/helpers/multisig.js +++ b/governance/helpers/multisig.js @@ -104,7 +104,7 @@ const getMultiSigInfo = async (chainId, multisig) => { const getProvider = async (chainId) => { let provider if (chainId) { - const { publicProvider } = networks[chainId] + const { provider: publicProvider } = networks[chainId] provider = new ethers.JsonRpcProvider(publicProvider) } else { ;({ provider } = ethers) diff --git a/governance/scripts/deployments/OPBridgedToken.js b/governance/scripts/deployments/OPBridgedToken.js index 6e7048c8c23..e2a92f8684b 100644 --- a/governance/scripts/deployments/OPBridgedToken.js +++ b/governance/scripts/deployments/OPBridgedToken.js @@ -10,8 +10,8 @@ const { ethers } = require('hardhat') const fs = require('fs-extra') // edit default values -const L1_CHAIN_ID = 11155111 // default to Sepolia -const l2_CHAIN_ID = 84532 // default to Base Sepolia +const L1_CHAIN_ID = 1 // default to (Sepolia 11155111) +const l2_CHAIN_ID = 10 // default to (Base Sepolia 84532) async function main({ tokenSymbol = 'UDT.e', @@ -21,7 +21,6 @@ async function main({ l2ChainId = l2_CHAIN_ID, } = {}) { const { DEPLOYER_PRIVATE_KEY } = process.env - const { name: l1Name, unlockDaoToken: { address: l1TokenAddress }, @@ -32,14 +31,13 @@ async function main({ } // get l2 network info const l2 = await getNetwork(l2ChainId) - console.log( `Deploying bridged token from L1 (${l1Name}) ${l1TokenAddress} to L2 ${l2.name} (${l2.id})... - token: ${l1TokenAddress} ` ) // Create the RPC providers and wallets - const l2Provider = await getProvider(l2ChainId) + const { provider: l2Provider } = await getProvider(l2ChainId) const l2Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l2Provider) // read ABI from node_modules From 2d01ebafd1f786b77d227c754485f3ab93ce2dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 13:17:23 +0100 Subject: [PATCH 22/33] add optimism bridged token --- packages/networks/src/networks/arbitrum.ts | 1 - packages/networks/src/networks/optimism.ts | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/networks/src/networks/arbitrum.ts b/packages/networks/src/networks/arbitrum.ts index ae8caabcf4c..a41481aa1f1 100644 --- a/packages/networks/src/networks/arbitrum.ts +++ b/packages/networks/src/networks/arbitrum.ts @@ -145,7 +145,6 @@ export const arbitrum: NetworkConfig = { unlockAddress: '0x1FF7e338d5E582138C46044dc238543Ce555C963', unlockDaoToken: { address: '0xd5d3aA404D7562d09a848F96a8a8d5D65977bF90', - // mainnetBridge: '0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf', }, url: 'https://arbitrum.io/', } diff --git a/packages/networks/src/networks/optimism.ts b/packages/networks/src/networks/optimism.ts index d818a26f854..8ec0b30f2a6 100644 --- a/packages/networks/src/networks/optimism.ts +++ b/packages/networks/src/networks/optimism.ts @@ -152,6 +152,9 @@ export const optimism: NetworkConfig = { universalRouterAddress: '0xb555edF5dcF85f42cEeF1f3630a52A108E55A654', }, unlockAddress: '0x99b1348a9129ac49c6de7F11245773dE2f51fB0c', + unlockDaoToken: { + address: '0x35CCe76D854c6E90F665C2B49899E158da9D517E', + }, url: 'https://www.optimism.io/', } From 1f359beadcbb16d5c1b1bd2dc1598fec033baede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 13:44:44 +0100 Subject: [PATCH 23/33] pass correct ERC20 abi --- .../contracts/interfaces/IMintableERC20.sol | 4 ++++ smart-contracts/scripts/bridge/arb.js | 4 +++- smart-contracts/scripts/bridge/op.js | 23 +++++++++++-------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/smart-contracts/contracts/interfaces/IMintableERC20.sol b/smart-contracts/contracts/interfaces/IMintableERC20.sol index 5e325d90a24..ef800f4a987 100644 --- a/smart-contracts/contracts/interfaces/IMintableERC20.sol +++ b/smart-contracts/contracts/interfaces/IMintableERC20.sol @@ -13,4 +13,8 @@ interface IMintableERC20 { function approve(address spender, uint256 amount) external returns (bool); function decimals() external view returns (uint8); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); } diff --git a/smart-contracts/scripts/bridge/arb.js b/smart-contracts/scripts/bridge/arb.js index 379020f41e0..f6d513b1073 100644 --- a/smart-contracts/scripts/bridge/arb.js +++ b/smart-contracts/scripts/bridge/arb.js @@ -63,7 +63,9 @@ async function main({ const balance = await l1Token.balanceOf(l1Wallet.address) console.log(`Balances: - L1 Wallet: ${balance.toString()} - - L1 Gateway / L2: ${await l1Token.balanceOf(expectedL1GatewayAddress)}`) + - L1 Gateway / L2 totalSupply: ${await l1Token.balanceOf( + expectedL1GatewayAddress + )}`) if (balance.lt(amount)) { throw new Error('Insufficient UDT balance on L1. Can not bridge') diff --git a/smart-contracts/scripts/bridge/op.js b/smart-contracts/scripts/bridge/op.js index a3033484809..e75c0b821fb 100644 --- a/smart-contracts/scripts/bridge/op.js +++ b/smart-contracts/scripts/bridge/op.js @@ -7,21 +7,18 @@ * uses v6. */ -const { - getERC20Contract, - getNetwork, -} = require('@unlock-protocol/hardhat-helpers') +const { getNetwork } = require('@unlock-protocol/hardhat-helpers') const { ethers } = require('hardhat') const optimism = require('@eth-optimism/sdk') // edit default values -const L1_CHAIN_ID = 11155111 // default to Sepolia -const l2_CHAIN_ID = 84532 // default to Base Sepolia +const L1_CHAIN_ID = 1 // Sepolia 11155111 +const l2_CHAIN_ID = 10 // Base Sepolia 84532 async function main({ l1ChainId = L1_CHAIN_ID, l2ChainId = l2_CHAIN_ID, - amount = 1000000000000000000n, // default to 1 + amount = 100000000000000000n, // default to 0.1 } = {}) { const { DEPLOYER_PRIVATE_KEY } = process.env @@ -46,8 +43,16 @@ async function main({ const l2Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l2Provider) // tokens - const l1Token = await getERC20Contract(l1TokenAddress, l1Wallet) - const l2Token = await getERC20Contract(l2TokenAddress, l2Wallet) + const l1Token = await ethers.getContractAt( + 'IMintableERC20', + l1TokenAddress, + l1Wallet + ) + const l2Token = await ethers.getContractAt( + 'IMintableERC20', + l2TokenAddress, + l2Wallet + ) console.log( `Amount: ${ethers.utils.formatUnits( From 724817532cb54f72a1e345c7b38170b70ed3650e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 13:45:04 +0100 Subject: [PATCH 24/33] deploy on base --- governance/scripts/deployments/OPBridgedToken.js | 11 +++++++++-- packages/networks/src/networks/base.ts | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/governance/scripts/deployments/OPBridgedToken.js b/governance/scripts/deployments/OPBridgedToken.js index e2a92f8684b..6f0c68eab6f 100644 --- a/governance/scripts/deployments/OPBridgedToken.js +++ b/governance/scripts/deployments/OPBridgedToken.js @@ -9,14 +9,21 @@ const { getProvider } = require('../../helpers/multisig') const { ethers } = require('hardhat') const fs = require('fs-extra') +// base factory address from https://docs.base.org/base-contracts/ +const BASE_OptimismMintableERC20Factory = + '0xf23d369d7471bD9f6487E198723eEa023389f1d4' + +// OP factory address +// const OP_OptimismMintableERC20Factory = '0x4200000000000000000000000000000000000012' + // edit default values const L1_CHAIN_ID = 1 // default to (Sepolia 11155111) -const l2_CHAIN_ID = 10 // default to (Base Sepolia 84532) +const l2_CHAIN_ID = 8453 // default to (Base Sepolia 84532) async function main({ tokenSymbol = 'UDT.e', tokenName = 'UnlockDiscountToken (bridged)', - factoryAddress = '0x4200000000000000000000000000000000000012', // bridged token factory address + factoryAddress = BASE_OptimismMintableERC20Factory, // bridged erc20 factory address l1ChainId = L1_CHAIN_ID, l2ChainId = l2_CHAIN_ID, } = {}) { diff --git a/packages/networks/src/networks/base.ts b/packages/networks/src/networks/base.ts index 0ea82385c8a..c3b7a863f85 100644 --- a/packages/networks/src/networks/base.ts +++ b/packages/networks/src/networks/base.ts @@ -125,6 +125,9 @@ export const base: NetworkConfig = { universalRouterAddress: '0x198EF79F1F515F02dFE9e3115eD9fC07183f02fC', }, unlockAddress: '0xd0b14797b9D08493392865647384974470202A78', + unlockDaoToken: { + address: '0xE68858e8fe7b491B13d1aefccBBC7367D1f1848e', + }, url: 'https://base.org/', } From f2f55b71f93aa7039426c83e05f9a10042e46612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 14:02:36 +0100 Subject: [PATCH 25/33] add base token bridge --- guides | 1 + .../scripts/bridge/abi/l1standardbridge.json | 521 ++++++++++++++++++ smart-contracts/scripts/bridge/base.js | 90 +++ 3 files changed, 612 insertions(+) create mode 160000 guides create mode 100644 smart-contracts/scripts/bridge/abi/l1standardbridge.json create mode 100644 smart-contracts/scripts/bridge/base.js diff --git a/guides b/guides new file mode 160000 index 00000000000..328bd50e39a --- /dev/null +++ b/guides @@ -0,0 +1 @@ +Subproject commit 328bd50e39a15056f5e0c49db2e55045967d55f2 diff --git a/smart-contracts/scripts/bridge/abi/l1standardbridge.json b/smart-contracts/scripts/bridge/abi/l1standardbridge.json new file mode 100644 index 00000000000..d7fcc0d0226 --- /dev/null +++ b/smart-contracts/scripts/bridge/abi/l1standardbridge.json @@ -0,0 +1,521 @@ +[ + { + "inputs": [ + { + "internalType": "address payable", + "name": "_messenger", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20BridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20BridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20DepositInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20WithdrawalFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHBridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHBridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHDepositInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHWithdrawalFinalized", + "type": "event" + }, + { + "inputs": [], + "name": "MESSENGER", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OTHER_BRIDGE", + "outputs": [ + { + "internalType": "contract StandardBridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_localToken", "type": "address" }, + { "internalType": "address", "name": "_remoteToken", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_localToken", "type": "address" }, + { "internalType": "address", "name": "_remoteToken", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeERC20To", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeETHTo", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_l1Token", "type": "address" }, + { "internalType": "address", "name": "_l2Token", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "depositERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_l1Token", "type": "address" }, + { "internalType": "address", "name": "_l2Token", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "depositERC20To", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "depositETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "depositETHTo", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "deposits", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_localToken", "type": "address" }, + { "internalType": "address", "name": "_remoteToken", "type": "address" }, + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "finalizeBridgeERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "finalizeBridgeETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_l1Token", "type": "address" }, + { "internalType": "address", "name": "_l2Token", "type": "address" }, + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "finalizeERC20Withdrawal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "finalizeETHWithdrawal", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "l2TokenBridge", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/smart-contracts/scripts/bridge/base.js b/smart-contracts/scripts/bridge/base.js new file mode 100644 index 00000000000..f88dee24a3a --- /dev/null +++ b/smart-contracts/scripts/bridge/base.js @@ -0,0 +1,90 @@ +/** + * Deploy a bridged ERC20 UDT token contract on Arbitrum network + * + * Please edit the chain ids constant below to use + * TODO: move to `governance` workspace - Arbitrum SDK requires ethers@5 + */ +const { getNetwork } = require('@unlock-protocol/hardhat-helpers') +const l1BridgeAbi = require('./abi/l1standardbridge.json') +const { ethers } = require('hardhat') + +const L1_CHAIN_ID = 1 // mainnet (Sepolia 11155111) +const l2_CHAIN_ID = 8453 // BASE (BASE Sepolia 8453x) +const l1StandardBridge = '0xfd0Bf71F60660E2f608ed56e1659C450eB113120' +const defaultGasAmount = '100000' + +async function main({ + l1ChainId = L1_CHAIN_ID, + l2ChainId = l2_CHAIN_ID, + amount = 100000000000000000n, // default to 0.1 +} = {}) { + const { DEPLOYER_PRIVATE_KEY } = process.env + + const [l1, l2] = await Promise.all([ + await getNetwork(l1ChainId), + await getNetwork(l2ChainId), + ]) + + const l1TokenAddress = l1.unlockDaoToken.address + const l2TokenAddress = l2.unlockDaoToken.address + + console.log( + `Bridging tokens from L1 ${l1.name} (${l1.id}) to L2 ${l2.name} (${l2.id})... + - L1: ${l1TokenAddress} + - L2: ${l2TokenAddress}` + ) + + // Create the RPC providers and wallets + const l1Provider = new ethers.providers.StaticJsonRpcProvider(l1.provider) + const l1Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l1Provider) + + // const l2Provider = new ethers.providers.StaticJsonRpcProvider(l2.provider) + // const l2Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l2Provider) + + const bridgeContract = new ethers.Contract( + l1StandardBridge, + l1BridgeAbi, + l1Wallet + ) + + const sender = await bridgeContract.l2TokenBridge() + console.log('sender', sender) + + const l1Token = await ethers.getContractAt('IERC20', l1TokenAddress, l1Wallet) + + // approval + const allowance = await l1Token.allowance(l1Wallet.address, l1StandardBridge) + if (allowance < amount) { + console.log('approve bridge to access token') + const approveResult = await l1Token.approve(l1StandardBridge, amount) + console.log('approve result', approveResult) + } else { + console.log('token is approved to deposit') + } + + // deposit to bridge + try { + const bridgeResult = await bridgeContract.depositERC20( + l1TokenAddress, + l2TokenAddress, + amount, + defaultGasAmount, + '0x' // pass empty data + ) + console.log('bridge token result', bridgeResult) + const transactionReceipt = await bridgeResult.wait() + console.log('token transaction receipt', transactionReceipt) + } catch (e) { + console.log('bridge token result error', e) + } +} + +// execute as standalone +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) +} From 4bae6d209195f8b6ed1965658a489887118f88c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Fri, 29 Mar 2024 14:43:47 +0100 Subject: [PATCH 26/33] basic base bridge token script --- smart-contracts/scripts/bridge/base.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/smart-contracts/scripts/bridge/base.js b/smart-contracts/scripts/bridge/base.js index f88dee24a3a..b39f4c1e92c 100644 --- a/smart-contracts/scripts/bridge/base.js +++ b/smart-contracts/scripts/bridge/base.js @@ -9,8 +9,8 @@ const l1BridgeAbi = require('./abi/l1standardbridge.json') const { ethers } = require('hardhat') const L1_CHAIN_ID = 1 // mainnet (Sepolia 11155111) -const l2_CHAIN_ID = 8453 // BASE (BASE Sepolia 8453x) -const l1StandardBridge = '0xfd0Bf71F60660E2f608ed56e1659C450eB113120' +const l2_CHAIN_ID = 8453 // BASE (BASE Sepolia 84534) +const l1StandardBridge = '0x3154Cf16ccdb4C6d922629664174b904d80F2C35' const defaultGasAmount = '100000' async function main({ @@ -38,9 +38,6 @@ async function main({ const l1Provider = new ethers.providers.StaticJsonRpcProvider(l1.provider) const l1Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l1Provider) - // const l2Provider = new ethers.providers.StaticJsonRpcProvider(l2.provider) - // const l2Wallet = new ethers.Wallet(DEPLOYER_PRIVATE_KEY, l2Provider) - const bridgeContract = new ethers.Contract( l1StandardBridge, l1BridgeAbi, From 6fd475eba42d6692046c98855bfbe86f427aaf93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 3 Apr 2024 17:10:22 +0200 Subject: [PATCH 27/33] rm unwanted git ref --- guides | 1 - 1 file changed, 1 deletion(-) delete mode 160000 guides diff --git a/guides b/guides deleted file mode 160000 index 328bd50e39a..00000000000 --- a/guides +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 328bd50e39a15056f5e0c49db2e55045967d55f2 From 572fead317d44d3022621792a80dde47d0fe1c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 3 Apr 2024 17:16:44 +0200 Subject: [PATCH 28/33] move all udt scripts to governance --- governance/package.json | 1 + .../scripts/udt}/bridge/abi/l1standardbridge.json | 0 .../scripts => governance/scripts/udt}/bridge/arb.js | 11 ++++++----- .../scripts => governance/scripts/udt}/bridge/base.js | 4 +--- .../scripts => governance/scripts/udt}/bridge/op.js | 6 +----- yarn.lock | 3 ++- 6 files changed, 11 insertions(+), 14 deletions(-) rename {smart-contracts/scripts => governance/scripts/udt}/bridge/abi/l1standardbridge.json (100%) rename {smart-contracts/scripts => governance/scripts/udt}/bridge/arb.js (92%) rename {smart-contracts/scripts => governance/scripts/udt}/bridge/base.js (93%) rename {smart-contracts/scripts => governance/scripts/udt}/bridge/op.js (91%) diff --git a/governance/package.json b/governance/package.json index 994913acd9f..bc144f7158e 100644 --- a/governance/package.json +++ b/governance/package.json @@ -23,6 +23,7 @@ "@unlock-protocol/networks": "workspace:./packages/networks", "eslint": "8.54.0", "ethers": "6.10.0", + "ethers5": "npm:ethers@5", "fs-extra": "11.2.0", "hardhat": "2.20.1", "solhint": "4.5.2", diff --git a/smart-contracts/scripts/bridge/abi/l1standardbridge.json b/governance/scripts/udt/bridge/abi/l1standardbridge.json similarity index 100% rename from smart-contracts/scripts/bridge/abi/l1standardbridge.json rename to governance/scripts/udt/bridge/abi/l1standardbridge.json diff --git a/smart-contracts/scripts/bridge/arb.js b/governance/scripts/udt/bridge/arb.js similarity index 92% rename from smart-contracts/scripts/bridge/arb.js rename to governance/scripts/udt/bridge/arb.js index f6d513b1073..e7e71244c94 100644 --- a/smart-contracts/scripts/bridge/arb.js +++ b/governance/scripts/udt/bridge/arb.js @@ -1,8 +1,8 @@ /** - * Deploy a bridged ERC20 UDT token contract on Arbitrum network + * Bridge UDT token to the Arbitrum network + * NB: if the contract for the bridge token is not deployed, + * this will deploy it. * - * Please edit the chain ids constant below to use - * TODO: move to `governance` workspace - Arbitrum SDK requires ethers@5 */ const { getNetwork } = require('@unlock-protocol/hardhat-helpers') @@ -11,7 +11,8 @@ const { Erc20Bridger, L1ToL2MessageStatus, } = require('@arbitrum/sdk') -const { ethers } = require('hardhat') +const ethers = require('ethers5') +const abiERC20 = require('@unlock-protocol/hardhat-helpers/dist/ABIs/erc20.json') const L1_CHAIN_ID = 1 // mainnet (Sepolia 11155111) const l2_CHAIN_ID = 42161 // ARB (ARB Sepolia 421614) @@ -43,7 +44,7 @@ async function main({ const l2Provider = new ethers.providers.StaticJsonRpcProvider(l2.provider) // token contract instance - const l1Token = await ethers.getContractAt('IERC20', l1TokenAddress, l1Wallet) + const l1Token = new ethers.Contract(abiERC20, l1TokenAddress, l1Wallet) // use arb sdk const l2Network = await getL2Network(l2Provider) diff --git a/smart-contracts/scripts/bridge/base.js b/governance/scripts/udt/bridge/base.js similarity index 93% rename from smart-contracts/scripts/bridge/base.js rename to governance/scripts/udt/bridge/base.js index b39f4c1e92c..f5038da7f50 100644 --- a/smart-contracts/scripts/bridge/base.js +++ b/governance/scripts/udt/bridge/base.js @@ -1,12 +1,10 @@ /** * Deploy a bridged ERC20 UDT token contract on Arbitrum network * - * Please edit the chain ids constant below to use - * TODO: move to `governance` workspace - Arbitrum SDK requires ethers@5 */ const { getNetwork } = require('@unlock-protocol/hardhat-helpers') const l1BridgeAbi = require('./abi/l1standardbridge.json') -const { ethers } = require('hardhat') +const ethers = require('ethers5') const L1_CHAIN_ID = 1 // mainnet (Sepolia 11155111) const l2_CHAIN_ID = 8453 // BASE (BASE Sepolia 84534) diff --git a/smart-contracts/scripts/bridge/op.js b/governance/scripts/udt/bridge/op.js similarity index 91% rename from smart-contracts/scripts/bridge/op.js rename to governance/scripts/udt/bridge/op.js index e75c0b821fb..020212a70c7 100644 --- a/smart-contracts/scripts/bridge/op.js +++ b/governance/scripts/udt/bridge/op.js @@ -1,14 +1,10 @@ /** * Bridge tokens to Optimism networks * - * TODO: move to governance foldder - * NB: this scripts belong to `governance` folder but lives here as the Optimsim sdk - * requires ethers v5 ( in use in this workspace), while the governance workspace - * uses v6. */ const { getNetwork } = require('@unlock-protocol/hardhat-helpers') -const { ethers } = require('hardhat') +const ethers = require('ethers5') const optimism = require('@eth-optimism/sdk') // edit default values diff --git a/yarn.lock b/yarn.lock index 6322c1d63dc..778acc8e8f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20441,6 +20441,7 @@ __metadata: "@unlock-protocol/networks": "workspace:./packages/networks" eslint: "npm:8.54.0" ethers: "npm:6.10.0" + ethers5: "npm:ethers@5" fs-extra: "npm:11.2.0" hardhat: "npm:2.20.1" solhint: "npm:4.5.2" @@ -31902,7 +31903,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:5.7.2, ethers@npm:^5.1.0, ethers@npm:^5.3.1, ethers@npm:^5.5.4, ethers@npm:^5.6.1, ethers@npm:^5.7.0, ethers@npm:^5.7.1, ethers@npm:^5.7.2": +"ethers5@npm:ethers@5, ethers@npm:5.7.2, ethers@npm:^5.1.0, ethers@npm:^5.3.1, ethers@npm:^5.5.4, ethers@npm:^5.6.1, ethers@npm:^5.7.0, ethers@npm:^5.7.1, ethers@npm:^5.7.2": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: From 38f8e2ba16102d52268e823434d2e24199112615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 3 Apr 2024 17:18:09 +0200 Subject: [PATCH 29/33] remove changes from smart-contracts folder --- governance/package.json | 2 ++ smart-contracts/contracts/interfaces/IMintableERC20.sol | 4 ---- smart-contracts/package.json | 2 -- yarn.lock | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/governance/package.json b/governance/package.json index bc144f7158e..60f1dfc7e8f 100644 --- a/governance/package.json +++ b/governance/package.json @@ -3,6 +3,8 @@ "description": "Scripts for the management of the Unlock Protocol", "private": true, "dependencies": { + "@arbitrum/sdk": "3.3.3", + "@eth-optimism/sdk": "3.2.3", "@matterlabs/hardhat-zksync-deploy": "1.1.2", "@matterlabs/hardhat-zksync-solc": "1.1.0", "@matterlabs/hardhat-zksync-upgradable": "1.2.4", diff --git a/smart-contracts/contracts/interfaces/IMintableERC20.sol b/smart-contracts/contracts/interfaces/IMintableERC20.sol index ef800f4a987..5e325d90a24 100644 --- a/smart-contracts/contracts/interfaces/IMintableERC20.sol +++ b/smart-contracts/contracts/interfaces/IMintableERC20.sol @@ -13,8 +13,4 @@ interface IMintableERC20 { function approve(address spender, uint256 amount) external returns (bool); function decimals() external view returns (uint8); - - function name() external view returns (string memory); - - function symbol() external view returns (string memory); } diff --git a/smart-contracts/package.json b/smart-contracts/package.json index f8640d283b8..f46675fe462 100644 --- a/smart-contracts/package.json +++ b/smart-contracts/package.json @@ -8,9 +8,7 @@ "test": "test" }, "dependencies": { - "@arbitrum/sdk": "3.3.3", "@connext/interfaces": "2.0.5", - "@eth-optimism/sdk": "3.2.3", "@nomiclabs/hardhat-ethers": "2.2.3", "@nomiclabs/hardhat-etherscan": "3.1.8", "@nomiclabs/hardhat-waffle": "2.0.6", diff --git a/yarn.lock b/yarn.lock index 778acc8e8f9..33d1660bc94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20421,6 +20421,8 @@ __metadata: version: 0.0.0-use.local resolution: "@unlock-protocol/governance@workspace:governance" dependencies: + "@arbitrum/sdk": "npm:3.3.3" + "@eth-optimism/sdk": "npm:3.2.3" "@matterlabs/hardhat-zksync-deploy": "npm:1.1.2" "@matterlabs/hardhat-zksync-solc": "npm:1.1.0" "@matterlabs/hardhat-zksync-upgradable": "npm:1.2.4" @@ -20653,9 +20655,7 @@ __metadata: version: 0.0.0-use.local resolution: "@unlock-protocol/smart-contracts@workspace:smart-contracts" dependencies: - "@arbitrum/sdk": "npm:3.3.3" "@connext/interfaces": "npm:2.0.5" - "@eth-optimism/sdk": "npm:3.2.3" "@nomiclabs/hardhat-ethers": "npm:2.2.3" "@nomiclabs/hardhat-etherscan": "npm:3.1.8" "@nomiclabs/hardhat-waffle": "npm:2.0.6" From 8f3ca9758733d16f260d5549ab57b24affa9d25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 3 Apr 2024 17:19:11 +0200 Subject: [PATCH 30/33] unchecked sepola deployments --- governance/.openzeppelin/sepolia.json | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/governance/.openzeppelin/sepolia.json b/governance/.openzeppelin/sepolia.json index f2e88140d52..21640906b54 100644 --- a/governance/.openzeppelin/sepolia.json +++ b/governance/.openzeppelin/sepolia.json @@ -19,16 +19,6 @@ "address": "0x447B1492C5038203f1927eB2a374F5Fcdc25999d", "txHash": "0xa552ab865e287870a8c75bca97243b7cdc1a73a60e3221c6198ffaec2cd155fa", "kind": "transparent" - }, - { - "address": "0xAF7B2b27274fa132728a26f611aAF589AB6d4f31", - "txHash": "0x73f52631491158978dee1c0d2ba69b3136d1d76b73b5b69eaadc59e0ed233bca", - "kind": "transparent" - }, - { - "address": "0x0B26203E3DE7E680c9749CFa47b7ea37fEE7bd98", - "txHash": "0xed5394f06743a9998e02ec842e0d0f1a7a18ca91a3ea307a9cd2c328d213ecce", - "kind": "transparent" } ], "impls": { @@ -1028,4 +1018,4 @@ } } } -} +} \ No newline at end of file From 8df8e3e9400875b691853d767026ae59b4c5934d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 3 Apr 2024 18:29:08 +0200 Subject: [PATCH 31/33] deploy UDT on base sepolia --- packages/networks/src/networks/base-sepolia.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/networks/src/networks/base-sepolia.ts b/packages/networks/src/networks/base-sepolia.ts index d2d6318ce6c..bc0f95d4504 100644 --- a/packages/networks/src/networks/base-sepolia.ts +++ b/packages/networks/src/networks/base-sepolia.ts @@ -68,7 +68,7 @@ export const baseSepolia: NetworkConfig = { ], unlockAddress: '0x259813B665C8f6074391028ef782e27B65840d89', unlockDaoToken: { - address: '', + address: '0x345F99F6873866f8b73aD4a500396c4635E0E755', }, } From 1ab97242961aceb5aa30056224bb75d1724a22f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Wed, 3 Apr 2024 18:29:43 +0200 Subject: [PATCH 32/33] improve base script --- .../{OPBridgedToken.js => OPBridgedERC20.js} | 19 ++++--- governance/scripts/udt/bridge/base.js | 54 ++++++++++++------- 2 files changed, 46 insertions(+), 27 deletions(-) rename governance/scripts/deployments/{OPBridgedToken.js => OPBridgedERC20.js} (79%) diff --git a/governance/scripts/deployments/OPBridgedToken.js b/governance/scripts/deployments/OPBridgedERC20.js similarity index 79% rename from governance/scripts/deployments/OPBridgedToken.js rename to governance/scripts/deployments/OPBridgedERC20.js index 6f0c68eab6f..ae1199fb818 100644 --- a/governance/scripts/deployments/OPBridgedToken.js +++ b/governance/scripts/deployments/OPBridgedERC20.js @@ -10,20 +10,23 @@ const { ethers } = require('hardhat') const fs = require('fs-extra') // base factory address from https://docs.base.org/base-contracts/ -const BASE_OptimismMintableERC20Factory = - '0xf23d369d7471bD9f6487E198723eEa023389f1d4' +const OptimismMintableERC20Factory = { + base: '0xf23d369d7471bD9f6487E198723eEa023389f1d4', + optimism: '0x4200000000000000000000000000000000000012', + baseSepolia: '0x4200000000000000000000000000000000000012', +} // OP factory address -// const OP_OptimismMintableERC20Factory = '0x4200000000000000000000000000000000000012' +// const OP_OptimismMintableERC20Factory = // edit default values -const L1_CHAIN_ID = 1 // default to (Sepolia 11155111) -const l2_CHAIN_ID = 8453 // default to (Base Sepolia 84532) +const L1_CHAIN_ID = 11155111 // default to (Sepolia 11155111) +const l2_CHAIN_ID = 84532 // default to (Base Sepolia 84532) async function main({ - tokenSymbol = 'UDT.e', - tokenName = 'UnlockDiscountToken (bridged)', - factoryAddress = BASE_OptimismMintableERC20Factory, // bridged erc20 factory address + tokenSymbol = 'UDT', + tokenName = 'UnlockDiscountToken', + factoryAddress = OptimismMintableERC20Factory.baseSepolia, // bridged erc20 factory address l1ChainId = L1_CHAIN_ID, l2ChainId = l2_CHAIN_ID, } = {}) { diff --git a/governance/scripts/udt/bridge/base.js b/governance/scripts/udt/bridge/base.js index f5038da7f50..ffe560cf4c9 100644 --- a/governance/scripts/udt/bridge/base.js +++ b/governance/scripts/udt/bridge/base.js @@ -5,16 +5,22 @@ const { getNetwork } = require('@unlock-protocol/hardhat-helpers') const l1BridgeAbi = require('./abi/l1standardbridge.json') const ethers = require('ethers5') +const abiERC20 = require('@unlock-protocol/hardhat-helpers/dist/ABIs/erc20.json') -const L1_CHAIN_ID = 1 // mainnet (Sepolia 11155111) -const l2_CHAIN_ID = 8453 // BASE (BASE Sepolia 84534) -const l1StandardBridge = '0x3154Cf16ccdb4C6d922629664174b904d80F2C35' -const defaultGasAmount = '100000' +const L1_CHAIN_ID = 11155111 // mainnet (Sepolia 11155111) +const l2_CHAIN_ID = 84532 // BASE (BASE Sepolia 84532) +const standardBridges = { + 11155111: '0x3154Cf16ccdb4C6d922629664174b904d80F2C35', + 84532: '0xfd0Bf71F60660E2f608ed56e1659C450eB113120', +} + +const defaultGasAmount = '500000' async function main({ l1ChainId = L1_CHAIN_ID, l2ChainId = l2_CHAIN_ID, - amount = 100000000000000000n, // default to 0.1 + l1StandardBridge = standardBridges[l2_CHAIN_ID], + amount = `100000000000000000`, // default to 0.1 } = {}) { const { DEPLOYER_PRIVATE_KEY } = process.env @@ -45,30 +51,40 @@ async function main({ const sender = await bridgeContract.l2TokenBridge() console.log('sender', sender) - const l1Token = await ethers.getContractAt('IERC20', l1TokenAddress, l1Wallet) + const l1Token = new ethers.Contract(l1TokenAddress, abiERC20, l1Wallet) + + // check balance + const balance = await l1Token.balanceOf(l1Wallet.address) + if (balance.lt(amount)) { + throw new Error(`Balance too low`) + } // approval const allowance = await l1Token.allowance(l1Wallet.address, l1StandardBridge) - if (allowance < amount) { - console.log('approve bridge to access token') - const approveResult = await l1Token.approve(l1StandardBridge, amount) - console.log('approve result', approveResult) + if (allowance.lt(amount)) { + console.log('approve bridge to access token...') + const { hash } = await l1Token.approve(l1StandardBridge, amount) + console.log(`approved (tx: ${hash})`) } else { console.log('token is approved to deposit') } + const bridgeArgs = { + l1TokenAddress, + l2TokenAddress, + amount, + defaultGasAmount, + emptyData: '0x', + } + // deposit to bridge try { - const bridgeResult = await bridgeContract.depositERC20( - l1TokenAddress, - l2TokenAddress, - amount, - defaultGasAmount, - '0x' // pass empty data + const bridgeResult = await bridgeContract.bridgeERC20( + ...Object.values(bridgeArgs) ) - console.log('bridge token result', bridgeResult) - const transactionReceipt = await bridgeResult.wait() - console.log('token transaction receipt', transactionReceipt) + console.log('bridge deposit ok', bridgeResult) + const { transactionHash } = await bridgeResult.wait() + console.log(`(tx: ${transactionHash})`) } catch (e) { console.log('bridge token result error', e) } From ceede7400394d80cab03ddf336fc18fb66634f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renaud?= Date: Thu, 4 Apr 2024 13:00:56 +0200 Subject: [PATCH 33/33] add gas estimate --- governance/scripts/udt/bridge/base.js | 57 +++++++++++++++++++-------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/governance/scripts/udt/bridge/base.js b/governance/scripts/udt/bridge/base.js index ffe560cf4c9..14b80300adf 100644 --- a/governance/scripts/udt/bridge/base.js +++ b/governance/scripts/udt/bridge/base.js @@ -7,19 +7,25 @@ const l1BridgeAbi = require('./abi/l1standardbridge.json') const ethers = require('ethers5') const abiERC20 = require('@unlock-protocol/hardhat-helpers/dist/ABIs/erc20.json') -const L1_CHAIN_ID = 11155111 // mainnet (Sepolia 11155111) -const l2_CHAIN_ID = 84532 // BASE (BASE Sepolia 84532) +// tweak these params +const USE_TESTNET = false +const SIMULATION = true +const gasLimit = 1000000 +const defaultGasAmount = '200000' + +// parse network details +const L1_CHAIN_ID = USE_TESTNET ? 11155111 : 1 // mainnet (Sepolia 11155111) +const l2_CHAIN_ID = USE_TESTNET ? 84532 : 8453 // BASE (BASE Sepolia 84532) const standardBridges = { - 11155111: '0x3154Cf16ccdb4C6d922629664174b904d80F2C35', - 84532: '0xfd0Bf71F60660E2f608ed56e1659C450eB113120', + 1: '0x3154Cf16ccdb4C6d922629664174b904d80F2C35', + 11155111: '0xfd0Bf71F60660E2f608ed56e1659C450eB113120', } - -const defaultGasAmount = '500000' +const L1_STANDARD_BRIDGE = standardBridges[L1_CHAIN_ID] async function main({ l1ChainId = L1_CHAIN_ID, l2ChainId = l2_CHAIN_ID, - l1StandardBridge = standardBridges[l2_CHAIN_ID], + l1StandardBridge = L1_STANDARD_BRIDGE, amount = `100000000000000000`, // default to 0.1 } = {}) { const { DEPLOYER_PRIVATE_KEY } = process.env @@ -66,7 +72,7 @@ async function main({ const { hash } = await l1Token.approve(l1StandardBridge, amount) console.log(`approved (tx: ${hash})`) } else { - console.log('token is approved to deposit') + console.log('token approval ok for deposit') } const bridgeArgs = { @@ -77,16 +83,33 @@ async function main({ emptyData: '0x', } + // show gas estimate + const gasEstimate = await bridgeContract.estimateGas.bridgeERC20( + ...Object.values(bridgeArgs), + { + gasLimit, + } + ) + const { baseFeePerGas } = await l1Provider.getBlock('latest') + const gasCostEstimate = baseFeePerGas.mul(gasEstimate) + console.log( + `Gas cost estimate: ${ethers.utils.formatEther(gasCostEstimate)} ETH` + ) // deposit to bridge - try { - const bridgeResult = await bridgeContract.bridgeERC20( - ...Object.values(bridgeArgs) - ) - console.log('bridge deposit ok', bridgeResult) - const { transactionHash } = await bridgeResult.wait() - console.log(`(tx: ${transactionHash})`) - } catch (e) { - console.log('bridge token result error', e) + if (!SIMULATION) { + try { + const bridgeResult = await bridgeContract.bridgeERC20( + ...Object.values(bridgeArgs), + { + gasLimit, + } + ) + console.log('bridge deposit done', bridgeResult) + const { transactionHash } = await bridgeResult.wait() + console.log(`(tx: ${transactionHash})`) + } catch (e) { + console.log('bridge token result error', e) + } } }