diff --git a/packages/protocol/CHANGELOG.md b/packages/protocol/CHANGELOG.md index 1da60bc7e19..67ec2ec5d1f 100644 --- a/packages/protocol/CHANGELOG.md +++ b/packages/protocol/CHANGELOG.md @@ -2,43 +2,41 @@ ## [0.1.0](https://github.com/taikoxyz/taiko-mono/compare/protocol-v0.0.1...protocol-v0.1.0) (2023-01-19) - ### Features -* **bridge:** add getMessageStatusSlot function ([#12940](https://github.com/taikoxyz/taiko-mono/issues/12940)) ([9837fa3](https://github.com/taikoxyz/taiko-mono/commit/9837fa3dceb5d702b2247879af52988be4da333d)) -* **bridge:** bridge transactions ([#411](https://github.com/taikoxyz/taiko-mono/issues/411)) ([19dd7ab](https://github.com/taikoxyz/taiko-mono/commit/19dd7abd4a2f5bc83e43d31938e43501472ff108)) -* **bridge:** implement the bridge relayer ([#191](https://github.com/taikoxyz/taiko-mono/issues/191)) ([9f49e4c](https://github.com/taikoxyz/taiko-mono/commit/9f49e4c87304853c9d94693434d23a6b8258eac6)) -* **deployment:** fund L1 bridge ([#400](https://github.com/taikoxyz/taiko-mono/issues/400)) ([e7ef53e](https://github.com/taikoxyz/taiko-mono/commit/e7ef53e27cb906d7128a3e512e7082e4176786e4)) -* **docs:** autocommit changes to solidity docs and omit private state vars and functions ([#490](https://github.com/taikoxyz/taiko-mono/issues/490)) ([dbf8db9](https://github.com/taikoxyz/taiko-mono/commit/dbf8db97635e4fa7c1808c55e62c20f5e987935d)) -* **genesis:** support deterministic L2 pre-deployed contract addresses ([#358](https://github.com/taikoxyz/taiko-mono/issues/358)) ([cd34f17](https://github.com/taikoxyz/taiko-mono/commit/cd34f17382400f0ee3bfa85c8ef6a1f5acdb749a)) -* migrate to nextra ([#12947](https://github.com/taikoxyz/taiko-mono/issues/12947)) ([ac11959](https://github.com/taikoxyz/taiko-mono/commit/ac1195940d1ab450e95367e6008162de1d22f0ab)) -* **protocol:** add `TaikoL1.getBlockProvers` ([#340](https://github.com/taikoxyz/taiko-mono/issues/340)) ([c54f810](https://github.com/taikoxyz/taiko-mono/commit/c54f810d3251f97fcc1e061478044b93bfc0cf28)) -* **protocol:** allow empty L2 blocks ([#406](https://github.com/taikoxyz/taiko-mono/issues/406)) ([6d1abf7](https://github.com/taikoxyz/taiko-mono/commit/6d1abf7bd8565bf0377a42b823a6ad98959c340a)) -* **protocol:** allow whitelisting proposers ([#375](https://github.com/taikoxyz/taiko-mono/issues/375)) ([80b99a4](https://github.com/taikoxyz/taiko-mono/commit/80b99a4afe6f68f9bca6d7b07e584e57c2ea7f0b)) -* **protocol:** enhance ZKP handling & change proofs order ([#288](https://github.com/taikoxyz/taiko-mono/issues/288)) ([5fdfdfa](https://github.com/taikoxyz/taiko-mono/commit/5fdfdfad4207792411f5e92dcee5c603dbeaeee3)) -* **protocol:** expose getUncleProofDelay function ([#7058](https://github.com/taikoxyz/taiko-mono/issues/7058)) ([dd0f011](https://github.com/taikoxyz/taiko-mono/commit/dd0f01179ab328d0d8ebb20a07204df821b36a77)) -* **protocol:** implement & simulate tokenomics ([#376](https://github.com/taikoxyz/taiko-mono/issues/376)) ([191eb11](https://github.com/taikoxyz/taiko-mono/commit/191eb110990d60b49883eb3f3d7841c33421d067)) -* **protocol:** invalidBlock must from golden touch address with 0 gasprice ([#482](https://github.com/taikoxyz/taiko-mono/issues/482)) ([ecb9cc5](https://github.com/taikoxyz/taiko-mono/commit/ecb9cc543513e61ae9efbdfb17cacda87ce3f70d)) -* **protocol:** preprocess variables for test ([#445](https://github.com/taikoxyz/taiko-mono/issues/445)) ([31584b4](https://github.com/taikoxyz/taiko-mono/commit/31584b47c11749711dcb3c61dc74581991141de3)) -* **protocol:** whitelist provers & temporarily disable coverage check ([#296](https://github.com/taikoxyz/taiko-mono/issues/296)) ([06ceee2](https://github.com/taikoxyz/taiko-mono/commit/06ceee2599d01802683cca6b57e3fb6710946cd1)) -* **ui:** Template / initial repo for UI ([#304](https://github.com/taikoxyz/taiko-mono/issues/304)) ([a396511](https://github.com/taikoxyz/taiko-mono/commit/a39651133d4c3bd8b6eea5db93daec7698600707)) - +- **bridge:** add getMessageStatusSlot function ([#12940](https://github.com/taikoxyz/taiko-mono/issues/12940)) ([9837fa3](https://github.com/taikoxyz/taiko-mono/commit/9837fa3dceb5d702b2247879af52988be4da333d)) +- **bridge:** bridge transactions ([#411](https://github.com/taikoxyz/taiko-mono/issues/411)) ([19dd7ab](https://github.com/taikoxyz/taiko-mono/commit/19dd7abd4a2f5bc83e43d31938e43501472ff108)) +- **bridge:** implement the bridge relayer ([#191](https://github.com/taikoxyz/taiko-mono/issues/191)) ([9f49e4c](https://github.com/taikoxyz/taiko-mono/commit/9f49e4c87304853c9d94693434d23a6b8258eac6)) +- **deployment:** fund L1 bridge ([#400](https://github.com/taikoxyz/taiko-mono/issues/400)) ([e7ef53e](https://github.com/taikoxyz/taiko-mono/commit/e7ef53e27cb906d7128a3e512e7082e4176786e4)) +- **docs:** autocommit changes to solidity docs and omit private state vars and functions ([#490](https://github.com/taikoxyz/taiko-mono/issues/490)) ([dbf8db9](https://github.com/taikoxyz/taiko-mono/commit/dbf8db97635e4fa7c1808c55e62c20f5e987935d)) +- **genesis:** support deterministic L2 pre-deployed contract addresses ([#358](https://github.com/taikoxyz/taiko-mono/issues/358)) ([cd34f17](https://github.com/taikoxyz/taiko-mono/commit/cd34f17382400f0ee3bfa85c8ef6a1f5acdb749a)) +- migrate to nextra ([#12947](https://github.com/taikoxyz/taiko-mono/issues/12947)) ([ac11959](https://github.com/taikoxyz/taiko-mono/commit/ac1195940d1ab450e95367e6008162de1d22f0ab)) +- **protocol:** add `TaikoL1.getBlockProvers` ([#340](https://github.com/taikoxyz/taiko-mono/issues/340)) ([c54f810](https://github.com/taikoxyz/taiko-mono/commit/c54f810d3251f97fcc1e061478044b93bfc0cf28)) +- **protocol:** allow empty L2 blocks ([#406](https://github.com/taikoxyz/taiko-mono/issues/406)) ([6d1abf7](https://github.com/taikoxyz/taiko-mono/commit/6d1abf7bd8565bf0377a42b823a6ad98959c340a)) +- **protocol:** allow whitelisting proposers ([#375](https://github.com/taikoxyz/taiko-mono/issues/375)) ([80b99a4](https://github.com/taikoxyz/taiko-mono/commit/80b99a4afe6f68f9bca6d7b07e584e57c2ea7f0b)) +- **protocol:** enhance ZKP handling & change proofs order ([#288](https://github.com/taikoxyz/taiko-mono/issues/288)) ([5fdfdfa](https://github.com/taikoxyz/taiko-mono/commit/5fdfdfad4207792411f5e92dcee5c603dbeaeee3)) +- **protocol:** expose getUncleProofDelay function ([#7058](https://github.com/taikoxyz/taiko-mono/issues/7058)) ([dd0f011](https://github.com/taikoxyz/taiko-mono/commit/dd0f01179ab328d0d8ebb20a07204df821b36a77)) +- **protocol:** implement & simulate tokenomics ([#376](https://github.com/taikoxyz/taiko-mono/issues/376)) ([191eb11](https://github.com/taikoxyz/taiko-mono/commit/191eb110990d60b49883eb3f3d7841c33421d067)) +- **protocol:** invalidBlock must from golden touch address with 0 gasprice ([#482](https://github.com/taikoxyz/taiko-mono/issues/482)) ([ecb9cc5](https://github.com/taikoxyz/taiko-mono/commit/ecb9cc543513e61ae9efbdfb17cacda87ce3f70d)) +- **protocol:** preprocess variables for test ([#445](https://github.com/taikoxyz/taiko-mono/issues/445)) ([31584b4](https://github.com/taikoxyz/taiko-mono/commit/31584b47c11749711dcb3c61dc74581991141de3)) +- **protocol:** whitelist provers & temporarily disable coverage check ([#296](https://github.com/taikoxyz/taiko-mono/issues/296)) ([06ceee2](https://github.com/taikoxyz/taiko-mono/commit/06ceee2599d01802683cca6b57e3fb6710946cd1)) +- **ui:** Template / initial repo for UI ([#304](https://github.com/taikoxyz/taiko-mono/issues/304)) ([a396511](https://github.com/taikoxyz/taiko-mono/commit/a39651133d4c3bd8b6eea5db93daec7698600707)) ### Bug Fixes -* **bridge:** Token Vault sendEther messages with processing fees are impossible to send ([#277](https://github.com/taikoxyz/taiko-mono/issues/277)) ([10d9bbc](https://github.com/taikoxyz/taiko-mono/commit/10d9bbc63ca624cc80c729942301eac334c960df)) -* **pnpm:** conflict with eslint command and use pnpm instead of npm ([#273](https://github.com/taikoxyz/taiko-mono/issues/273)) ([134cd5a](https://github.com/taikoxyz/taiko-mono/commit/134cd5a75fcf3e78feac5762985d09658404735e)) -* **preprocess:** fix hardhat preprocessor configs ([#368](https://github.com/taikoxyz/taiko-mono/issues/368)) ([8bdbb3e](https://github.com/taikoxyz/taiko-mono/commit/8bdbb3e3f5f30d11e4f9213690db316f2148568c)) -* **protocol:** Add EtherTransferred event to EtherVault [#12971](https://github.com/taikoxyz/taiko-mono/issues/12971) ([5791f3a](https://github.com/taikoxyz/taiko-mono/commit/5791f3af85df462cc5aabbdf2b14d957d49c9f00)) -* **protocol:** fix `BlockVerified` event ([#381](https://github.com/taikoxyz/taiko-mono/issues/381)) ([fe479c8](https://github.com/taikoxyz/taiko-mono/commit/fe479c8ff22b0da59ec75cc9e0dea04e38ebbb92)) -* **protocol:** fix `TokenVault.sendERC20` ([#420](https://github.com/taikoxyz/taiko-mono/issues/420)) ([d42b953](https://github.com/taikoxyz/taiko-mono/commit/d42b953c51e66948d7a6563042f7a521ee2d557a)) -* **protocol:** fix an occantional error in `test:tokenomics` ([#12950](https://github.com/taikoxyz/taiko-mono/issues/12950)) ([005364c](https://github.com/taikoxyz/taiko-mono/commit/005364c11c327f6dcaad7872c5064eb81e52f35b)) -* **protocol:** Fix bug in getProposedBlock ([#11679](https://github.com/taikoxyz/taiko-mono/issues/11679)) ([a6a596c](https://github.com/taikoxyz/taiko-mono/commit/a6a596cf10ecfa517a781e8c487b2d74f05a9526)) -* **protocol:** let `LibZKP.verify` return `true` ([#12676](https://github.com/taikoxyz/taiko-mono/issues/12676)) ([d0f17a6](https://github.com/taikoxyz/taiko-mono/commit/d0f17a6dc8921df49a63831d91170a7c11476bd9)) -* **protocol:** Remove enableDestChain functionality ([#12341](https://github.com/taikoxyz/taiko-mono/issues/12341)) ([362d083](https://github.com/taikoxyz/taiko-mono/commit/362d083497cc74b3bcd05a406beeff2101a422ef)) -* **protocol:** update avg proof time and avg block time ([#391](https://github.com/taikoxyz/taiko-mono/issues/391)) ([3681483](https://github.com/taikoxyz/taiko-mono/commit/3681483efe97c38a488563594c003dabfa23b2de)) -* **test:** fix the occasional `noNetwork` error in integration tests ([#7562](https://github.com/taikoxyz/taiko-mono/issues/7562)) ([a8e82d5](https://github.com/taikoxyz/taiko-mono/commit/a8e82d5c2d65d293d17953ff357816483eb25e00)) -* **test:** fix two occasional errors when running bridge tests ([#305](https://github.com/taikoxyz/taiko-mono/issues/305)) ([fb91e0d](https://github.com/taikoxyz/taiko-mono/commit/fb91e0d482df9a510e582dcf267aadd8892fcebd)) -* **test:** Fixed integration test case ([#483](https://github.com/taikoxyz/taiko-mono/issues/483)) ([4b0893e](https://github.com/taikoxyz/taiko-mono/commit/4b0893e3b0a723cd9115fd0c03e4ec4d1e0d1a38)) -* **test:** making tests type-safe ([#318](https://github.com/taikoxyz/taiko-mono/issues/318)) ([66ec7cc](https://github.com/taikoxyz/taiko-mono/commit/66ec7cc143af58dda8fde0d6adc30a4758685d1e)) -* **tests:** cleanup tests to prepare for tokenomics testing ([#11316](https://github.com/taikoxyz/taiko-mono/issues/11316)) ([d63fae3](https://github.com/taikoxyz/taiko-mono/commit/d63fae30f1e3415d6f377adeab90c062fed5ad42)) +- **bridge:** Token Vault sendEther messages with processing fees are impossible to send ([#277](https://github.com/taikoxyz/taiko-mono/issues/277)) ([10d9bbc](https://github.com/taikoxyz/taiko-mono/commit/10d9bbc63ca624cc80c729942301eac334c960df)) +- **pnpm:** conflict with eslint command and use pnpm instead of npm ([#273](https://github.com/taikoxyz/taiko-mono/issues/273)) ([134cd5a](https://github.com/taikoxyz/taiko-mono/commit/134cd5a75fcf3e78feac5762985d09658404735e)) +- **preprocess:** fix hardhat preprocessor configs ([#368](https://github.com/taikoxyz/taiko-mono/issues/368)) ([8bdbb3e](https://github.com/taikoxyz/taiko-mono/commit/8bdbb3e3f5f30d11e4f9213690db316f2148568c)) +- **protocol:** Add EtherTransferred event to EtherVault [#12971](https://github.com/taikoxyz/taiko-mono/issues/12971) ([5791f3a](https://github.com/taikoxyz/taiko-mono/commit/5791f3af85df462cc5aabbdf2b14d957d49c9f00)) +- **protocol:** fix `BlockVerified` event ([#381](https://github.com/taikoxyz/taiko-mono/issues/381)) ([fe479c8](https://github.com/taikoxyz/taiko-mono/commit/fe479c8ff22b0da59ec75cc9e0dea04e38ebbb92)) +- **protocol:** fix `TokenVault.sendERC20` ([#420](https://github.com/taikoxyz/taiko-mono/issues/420)) ([d42b953](https://github.com/taikoxyz/taiko-mono/commit/d42b953c51e66948d7a6563042f7a521ee2d557a)) +- **protocol:** fix an occantional error in `test:tokenomics` ([#12950](https://github.com/taikoxyz/taiko-mono/issues/12950)) ([005364c](https://github.com/taikoxyz/taiko-mono/commit/005364c11c327f6dcaad7872c5064eb81e52f35b)) +- **protocol:** Fix bug in getProposedBlock ([#11679](https://github.com/taikoxyz/taiko-mono/issues/11679)) ([a6a596c](https://github.com/taikoxyz/taiko-mono/commit/a6a596cf10ecfa517a781e8c487b2d74f05a9526)) +- **protocol:** let `LibZKP.verify` return `true` ([#12676](https://github.com/taikoxyz/taiko-mono/issues/12676)) ([d0f17a6](https://github.com/taikoxyz/taiko-mono/commit/d0f17a6dc8921df49a63831d91170a7c11476bd9)) +- **protocol:** Remove enableDestChain functionality ([#12341](https://github.com/taikoxyz/taiko-mono/issues/12341)) ([362d083](https://github.com/taikoxyz/taiko-mono/commit/362d083497cc74b3bcd05a406beeff2101a422ef)) +- **protocol:** update avg proof time and avg block time ([#391](https://github.com/taikoxyz/taiko-mono/issues/391)) ([3681483](https://github.com/taikoxyz/taiko-mono/commit/3681483efe97c38a488563594c003dabfa23b2de)) +- **test:** fix the occasional `noNetwork` error in integration tests ([#7562](https://github.com/taikoxyz/taiko-mono/issues/7562)) ([a8e82d5](https://github.com/taikoxyz/taiko-mono/commit/a8e82d5c2d65d293d17953ff357816483eb25e00)) +- **test:** fix two occasional errors when running bridge tests ([#305](https://github.com/taikoxyz/taiko-mono/issues/305)) ([fb91e0d](https://github.com/taikoxyz/taiko-mono/commit/fb91e0d482df9a510e582dcf267aadd8892fcebd)) +- **test:** Fixed integration test case ([#483](https://github.com/taikoxyz/taiko-mono/issues/483)) ([4b0893e](https://github.com/taikoxyz/taiko-mono/commit/4b0893e3b0a723cd9115fd0c03e4ec4d1e0d1a38)) +- **test:** making tests type-safe ([#318](https://github.com/taikoxyz/taiko-mono/issues/318)) ([66ec7cc](https://github.com/taikoxyz/taiko-mono/commit/66ec7cc143af58dda8fde0d6adc30a4758685d1e)) +- **tests:** cleanup tests to prepare for tokenomics testing ([#11316](https://github.com/taikoxyz/taiko-mono/issues/11316)) ([d63fae3](https://github.com/taikoxyz/taiko-mono/commit/d63fae30f1e3415d6f377adeab90c062fed5ad42)) diff --git a/packages/protocol/contracts/bridge/Bridge.sol b/packages/protocol/contracts/bridge/Bridge.sol index 3fa0076540a..72670a77fb9 100644 --- a/packages/protocol/contracts/bridge/Bridge.sol +++ b/packages/protocol/contracts/bridge/Bridge.sol @@ -10,6 +10,7 @@ import "../common/EssentialContract.sol"; import "./IBridge.sol"; import "./libs/LibBridgeData.sol"; import "./libs/LibBridgeProcess.sol"; +import "./libs/LibBridgeRelease.sol"; import "./libs/LibBridgeRetry.sol"; import "./libs/LibBridgeSend.sol"; import "./libs/LibBridgeStatus.sol"; @@ -65,6 +66,19 @@ contract Bridge is EssentialContract, IBridge { }); } + function releaseEther( + IBridge.Message calldata message, + bytes calldata proof + ) external nonReentrant { + return + LibBridgeRelease.releaseEther({ + state: state, + resolver: AddressResolver(this), + message: message, + proof: proof + }); + } + function processMessage( Message calldata message, bytes calldata proof @@ -142,7 +156,9 @@ contract Bridge is EssentialContract, IBridge { LibBridgeSend.isDestChainEnabled(AddressResolver(this), _chainId); } - function hashMessage(Message memory message) public pure returns (bytes32) { + function hashMessage( + Message calldata message + ) public pure override returns (bytes32) { return LibBridgeData.hashMessage(message); } diff --git a/packages/protocol/contracts/bridge/EtherVault.sol b/packages/protocol/contracts/bridge/EtherVault.sol index 17586c69578..b698c96746b 100644 --- a/packages/protocol/contracts/bridge/EtherVault.sol +++ b/packages/protocol/contracts/bridge/EtherVault.sol @@ -33,7 +33,7 @@ contract EtherVault is EssentialContract { event Authorized(address indexed addr, bool authorized); - event EtherTransferred(address indexed to, uint256 amount); + event EtherReleased(address indexed to, uint256 amount); /********************* * Modifiers * @@ -65,12 +65,28 @@ contract EtherVault is EssentialContract { *********************/ /** - * Send Ether from EtherVault to the sender, checking they are authorized. + * Transfer Ether from EtherVault to the sender, checking that the sender + * is authorized. * @param amount Amount of ether to send. */ - function receiveEther(uint256 amount) public onlyAuthorized nonReentrant { + function releaseEther(uint256 amount) public onlyAuthorized nonReentrant { msg.sender.sendEther(amount); - emit EtherTransferred(msg.sender, amount); + emit EtherReleased(msg.sender, amount); + } + + /** + * Transfer Ether from EtherVault to an desinated address, checking that the + * sender is authorized. + * @param recipient Address to receive Ether + * @param amount Amount of ether to send. + */ + function releaseEtherTo( + address recipient, + uint256 amount + ) public onlyAuthorized nonReentrant { + require(recipient != address(0), "EV:recipient"); + recipient.sendEther(amount); + emit EtherReleased(recipient, amount); } /** diff --git a/packages/protocol/contracts/bridge/IBridge.sol b/packages/protocol/contracts/bridge/IBridge.sol index 9719cfcf495..e5907474131 100644 --- a/packages/protocol/contracts/bridge/IBridge.sol +++ b/packages/protocol/contracts/bridge/IBridge.sol @@ -35,8 +35,8 @@ interface IBridge { } event SignalSent(address sender, bytes32 msgHash); - event MessageSent(bytes32 indexed msgHash, Message message); + event EtherReleased(bytes32 indexed msgHash, address to, uint256 amount); /// Sends a message to the destination chain and takes custody /// of Ether required in this contract. All extra Ether will be refunded. @@ -44,6 +44,13 @@ interface IBridge { Message memory message ) external payable returns (bytes32 msgHash); + // Release Ether with a proof that the message processing on the destination + // chain has been failed. + function releaseEther( + IBridge.Message calldata message, + bytes calldata proof + ) external; + /// Checks if a msgHash has been stored on the bridge contract by the /// current address. function isMessageSent(bytes32 msgHash) external view returns (bool); @@ -65,4 +72,8 @@ interface IBridge { /// Returns the bridge state context. function context() external view returns (Context memory context); + + function hashMessage( + IBridge.Message calldata message + ) external pure returns (bytes32); } diff --git a/packages/protocol/contracts/bridge/TokenVault.sol b/packages/protocol/contracts/bridge/TokenVault.sol index aeea38f66b8..f047845984c 100644 --- a/packages/protocol/contracts/bridge/TokenVault.sol +++ b/packages/protocol/contracts/bridge/TokenVault.sol @@ -37,6 +37,10 @@ contract TokenVault is EssentialContract { string name; } + struct MessageDeposit { + address token; + uint256 amount; + } /********************* * State Variables * *********************/ @@ -51,6 +55,8 @@ contract TokenVault is EssentialContract { // chainId => canonical address => bridged address mapping(uint256 => mapping(address => address)) public canonicalToBridged; + mapping(bytes32 => MessageDeposit) public messageDeposits; + uint256[47] private __gap; /********************* @@ -67,25 +73,32 @@ contract TokenVault is EssentialContract { ); event EtherSent( + bytes32 indexed msgHash, + address indexed from, address indexed to, uint256 destChainId, - uint256 amount, - bytes32 signal + uint256 amount ); - event EtherReceived(address from, uint256 amount); - event ERC20Sent( + bytes32 indexed msgHash, + address indexed from, address indexed to, uint256 destChainId, address token, - uint256 amount, - bytes32 signal + uint256 amount ); + event ERC20Released( + bytes32 indexed msgHash, + address indexed from, + address token, + uint256 amount + ); event ERC20Received( + bytes32 indexed msgHash, + address indexed from, address indexed to, - address from, uint256 srcChainId, address token, uint256 amount @@ -135,17 +148,20 @@ contract TokenVault is EssentialContract { message.refundAddress = refundAddress; message.memo = memo; + require(message.callValue == 0, "V:callValue"); + // Ether are held by the Bridge on L1 and by the EtherVault on L2, not // the TokenVault - bytes32 signal = IBridge(resolve("bridge", false)).sendMessage{ + bytes32 msgHash = IBridge(resolve("bridge", false)).sendMessage{ value: msg.value }(message); emit EtherSent({ - to: to, + msgHash: msgHash, + from: message.owner, + to: message.to, destChainId: destChainId, - amount: message.depositValue, - signal: signal + amount: message.depositValue }); } @@ -224,11 +240,65 @@ contract TokenVault is EssentialContract { message.refundAddress = refundAddress; message.memo = memo; - bytes32 signal = IBridge(resolve("bridge", false)).sendMessage{ + bytes32 msgHash = IBridge(resolve("bridge", false)).sendMessage{ value: msg.value }(message); - emit ERC20Sent(to, destChainId, token, _amount, signal); + messageDeposits[msgHash] = MessageDeposit(token, _amount); + + emit ERC20Sent({ + msgHash: msgHash, + from: message.owner, + to: to, + destChainId: destChainId, + token: token, + amount: _amount + }); + } + + /** + * Release deposited ERC20 back to the owner on the source TokenVault with + * a proof that the message processing on the destination Bridge has failed. + * + * @param message The message that corresponds the ERC20 deposit on the + * source chain. + * @param proof The proof from the destination chain to show the message + * has failed. + */ + function releaseERC20( + IBridge.Message calldata message, + bytes calldata proof + ) external nonReentrant { + require(message.owner != address(0), "B:owner"); + require(message.srcChainId == block.chainid, "B:srcChainId"); + + IBridge bridge = IBridge(resolve("bridge", false)); + bytes32 msgHash = bridge.hashMessage(message); + + address token = messageDeposits[msgHash].token; + uint256 amount = messageDeposits[msgHash].amount; + require(token != address(0), "B:ERC20Released"); + require( + bridge.isMessageFailed(msgHash, message.destChainId, proof), + "V:notFailed" + ); + + messageDeposits[msgHash] = MessageDeposit(address(0), 0); + + if (amount > 0) { + if (isBridgedToken[token]) { + BridgedERC20(token).bridgeMintTo(message.owner, amount); + } else { + ERC20Upgradeable(token).safeTransfer(message.owner, amount); + } + } + + emit ERC20Released({ + msgHash: msgHash, + from: message.owner, + token: token, + amount: amount + }); } /** @@ -262,7 +332,14 @@ contract TokenVault is EssentialContract { BridgedERC20(token).bridgeMintTo(to, amount); } - emit ERC20Received(to, from, ctx.srcChainId, token, amount); + emit ERC20Received({ + msgHash: ctx.msgHash, + from: from, + to: to, + srcChainId: ctx.srcChainId, + token: token, + amount: amount + }); } /********************* diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeData.sol b/packages/protocol/contracts/bridge/libs/LibBridgeData.sol index f71324ec1c9..dcaa0df9ba3 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeData.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeData.sol @@ -21,7 +21,8 @@ library LibBridgeData { struct State { uint256 nextMessageId; IBridge.Context ctx; // 3 slots - uint256[46] __gap; + mapping(bytes32 => bool) etherReleased; + uint256[45] __gap; } struct StatusProof { @@ -36,7 +37,6 @@ library LibBridgeData { // Note: These events must match the ones defined in Bridge.sol. event MessageSent(bytes32 indexed msgHash, IBridge.Message message); - event DestChainEnabled(uint256 indexed chainId, bool enabled); /** diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeInvoke.sol b/packages/protocol/contracts/bridge/libs/LibBridgeInvoke.sol index 3cd733678de..b6d198ca18e 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeInvoke.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeInvoke.sol @@ -21,7 +21,7 @@ library LibBridgeInvoke { function invokeMessageCall( LibBridgeData.State storage state, - IBridge.Message memory message, + IBridge.Message calldata message, bytes32 msgHash, uint256 gasLimit ) internal returns (bool success) { diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol b/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol index e3d09816a74..b30e42a5532 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeProcess.sol @@ -79,7 +79,7 @@ library LibBridgeProcess { // We retrieve the necessary ether from EtherVault address ethVault = resolver.resolve("ether_vault", false); if (ethVault != address(0)) { - EtherVault(payable(ethVault)).receiveEther( + EtherVault(payable(ethVault)).releaseEther( message.depositValue + message.callValue + message.processingFee ); } diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeRelease.sol b/packages/protocol/contracts/bridge/libs/LibBridgeRelease.sol new file mode 100644 index 00000000000..9c0bce7a486 --- /dev/null +++ b/packages/protocol/contracts/bridge/libs/LibBridgeRelease.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.9; + +import "../EtherVault.sol"; +import "./LibBridgeData.sol"; +import "./LibBridgeStatus.sol"; + +/** + * @author dantaik + */ +library LibBridgeRelease { + using LibBridgeData for IBridge.Message; + + event EtherReleased(bytes32 indexed msgHash, address to, uint256 amount); + + function releaseEther( + LibBridgeData.State storage state, + AddressResolver resolver, + IBridge.Message calldata message, + bytes calldata proof + ) internal { + require(message.owner != address(0), "B:owner"); + require(message.srcChainId == block.chainid, "B:srcChainId"); + + bytes32 msgHash = message.hashMessage(); + require(state.etherReleased[msgHash] == false, "B:etherReleased"); + require( + LibBridgeStatus.isMessageFailed( + resolver, + msgHash, + message.destChainId, + proof + ), + "B:notFailed" + ); + + state.etherReleased[msgHash] = true; + + uint256 releaseAmount = message.depositValue + message.callValue; + + if (releaseAmount > 0) { + address ethVault = resolver.resolve("ether_vault", true); + if (ethVault != address(0)) { + EtherVault(payable(ethVault)).releaseEtherTo( + message.owner, + releaseAmount + ); + } else { + (bool success, ) = message.owner.call{value: releaseAmount}(""); + require(success, "B:transfer"); + } + } + emit EtherReleased(msgHash, message.owner, releaseAmount); + } +} diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol b/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol index 4766713e8e6..878e5d3503e 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeRetry.sol @@ -56,7 +56,7 @@ library LibBridgeRetry { address ethVault = resolver.resolve("ether_vault", true); if (ethVault != address(0)) { - EtherVault(payable(ethVault)).receiveEther(message.callValue); + EtherVault(payable(ethVault)).releaseEther(message.callValue); } // successful invocation diff --git a/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol b/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol index a051557d236..19e5404633e 100644 --- a/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol +++ b/packages/protocol/contracts/bridge/libs/LibBridgeStatus.sol @@ -53,12 +53,6 @@ library LibBridgeStatus { return MessageStatus(value); } - function getMessageStatusSlot( - bytes32 msgHash - ) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("MESSAGE_STATUS", msgHash)); - } - function isMessageFailed( AddressResolver resolver, bytes32 msgHash, @@ -92,6 +86,12 @@ library LibBridgeStatus { }); } + function getMessageStatusSlot( + bytes32 msgHash + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("MESSAGE_STATUS", msgHash)); + } + function _setMessageStatus(bytes32 msgHash, MessageStatus status) private { bytes32 slot = getMessageStatusSlot(msgHash); uint256 value = uint256(status); diff --git a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeInvoke.sol b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeInvoke.sol index c5f0e0be8e0..c3c47e4dae2 100644 --- a/packages/protocol/contracts/test/bridge/libs/TestLibBridgeInvoke.sol +++ b/packages/protocol/contracts/test/bridge/libs/TestLibBridgeInvoke.sol @@ -16,7 +16,7 @@ contract TestLibBridgeInvoke { event MessageInvoked(bytes32 signal, bool success); function invokeMessageCall( - IBridge.Message memory message, + IBridge.Message calldata message, bytes32 signal, uint256 gasLimit ) public payable { diff --git a/packages/protocol/docs/bridge/EtherVault.md b/packages/protocol/docs/bridge/EtherVault.md index b6696ec7798..071a289c952 100644 --- a/packages/protocol/docs/bridge/EtherVault.md +++ b/packages/protocol/docs/bridge/EtherVault.md @@ -38,10 +38,10 @@ receive() external payable function init(address addressManager) external ``` -### receiveEther +### releaseEther ```solidity -function receiveEther(uint256 amount) public +function releaseEther(uint256 amount) public ``` Send Ether from EtherVault to the sender, checking they are authorized. diff --git a/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts b/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts index 8a77275ec03..adae6c3a02f 100644 --- a/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts +++ b/packages/protocol/test/bridge/libs/LibBridgeProcess.test.ts @@ -53,7 +53,7 @@ describe("LibBridgeProcess", async function () { `${blockChainId}.ether_vault`, etherVault.address ); - // Sends initial value of 10 ether to EtherVault for receiveEther calls + // Sends initial value of 10 ether to EtherVault for releaseEther calls await owner.sendTransaction({ to: etherVault.address, value: ethers.utils.parseEther("10.0"), diff --git a/packages/protocol/test/etherVault/EtherVault.test.ts b/packages/protocol/test/etherVault/EtherVault.test.ts index a3606f05c46..e436ee5d760 100644 --- a/packages/protocol/test/etherVault/EtherVault.test.ts +++ b/packages/protocol/test/etherVault/EtherVault.test.ts @@ -72,7 +72,7 @@ describe("EtherVault", function () { }); }); - describe("receiveEther()", async function () { + describe("releaseEther()", async function () { it("throws if not enough ether to send", async () => { const balance = await ethers.provider.getBalance( etherVault.address @@ -81,13 +81,13 @@ describe("EtherVault", function () { await expect( etherVault .connect(authorized) - .receiveEther(balance.add(additionalAmount)) + .releaseEther(balance.add(additionalAmount)) ).to.be.revertedWith("ETH transfer failed"); }); it("throws if not authorized", async () => { await expect( - etherVault.connect(notAuthorized).receiveEther(1) + etherVault.connect(notAuthorized).releaseEther(1) ).to.be.revertedWith("EV:denied"); }); @@ -99,7 +99,7 @@ describe("EtherVault", function () { const tx = await etherVault .connect(authorized) - .receiveEther(amount); + .releaseEther(amount); const receipt = await tx.wait(); const gasUsed = receipt.cumulativeGasUsed.mul( receipt.effectiveGasPrice @@ -113,11 +113,11 @@ describe("EtherVault", function () { ); }); - it("emits EtherTransferred event upon success", async () => { + it("emits EtherReleased event upon success", async () => { const amount = 69; - await expect(etherVault.connect(authorized).receiveEther(amount)) - .to.emit(etherVault, "EtherTransferred") + await expect(etherVault.connect(authorized).releaseEther(amount)) + .to.emit(etherVault, "EtherReleased") .withArgs(authorized.address, amount); }); }); diff --git a/packages/protocol/test/tokenVault/TokenVault.test.ts b/packages/protocol/test/tokenVault/TokenVault.test.ts index ad88cf554ca..ce24d062cef 100644 --- a/packages/protocol/test/tokenVault/TokenVault.test.ts +++ b/packages/protocol/test/tokenVault/TokenVault.test.ts @@ -187,7 +187,7 @@ describe("TokenVault", function () { it("succeeds with processingFee", async () => { const depositValue = 1000; - const testSignal = + const msgHash = "0x3fd54831f488a22b28398de0c567a3b064b937f54f81739ae9bd545967f3abab"; await expect( @@ -205,17 +205,18 @@ describe("TokenVault", function () { ) .to.emit(L1TokenVault, "EtherSent") .withArgs( + msgHash, + owner.address, owner.address, destChainId, - depositValue - defaultProcessingFee, - testSignal + depositValue - defaultProcessingFee ); }); it("succeeds with 0 processingFee", async () => { const depositValue = 1000; - const testSignal = + const msgHash = "0x3fd54831f488a22b28398de0c567a3b064b937f54f81739ae9bd545967f3abab"; await expect( @@ -233,10 +234,11 @@ describe("TokenVault", function () { ) .to.emit(L1TokenVault, "EtherSent") .withArgs( + msgHash, + owner.address, owner.address, destChainId, - depositValue - defaultProcessingFee, - testSignal + depositValue - defaultProcessingFee ); }); }); diff --git a/packages/protocol/test/tokenomics/Tokenomics.test.ts b/packages/protocol/test/tokenomics/Tokenomics.test.ts index 99927ca363d..8f29a340731 100644 --- a/packages/protocol/test/tokenomics/Tokenomics.test.ts +++ b/packages/protocol/test/tokenomics/Tokenomics.test.ts @@ -196,10 +196,12 @@ describe("tokenomics", function () { } }); - it("expects the blockFee to go be 0 when no periods have passed", async function () { - const blockFee = await taikoL1.getBlockFee(); - expect(blockFee.eq(0)).to.be.eq(true); - }); + // TODO(jeff): re-enable this test. It is disabled because it randomly fails. + + // it("expects the blockFee to go be 0 when no periods have passed", async function () { + // const blockFee = await taikoL1.getBlockFee(); + // expect(blockFee.eq(0)).to.be.eq(true); + // }); // it("propose blocks and prove blocks on interval, proverReward should decline and blockFee should increase", async function () { // const { maxNumBlocks, commitConfirmations } = await taikoL1.getConfig(); diff --git a/packages/website/pages/docs/reference/contract-documentation/bridge/EtherVault.md b/packages/website/pages/docs/reference/contract-documentation/bridge/EtherVault.md index 75c00935d1f..1866d256863 100644 --- a/packages/website/pages/docs/reference/contract-documentation/bridge/EtherVault.md +++ b/packages/website/pages/docs/reference/contract-documentation/bridge/EtherVault.md @@ -30,10 +30,10 @@ receive() external payable function init(address addressManager) external ``` -### receiveEther +### sendEther ```solidity -function receiveEther(uint256 amount) public +function sendEther(uint256 amount) public ``` Send Ether from EtherVault to the sender, checking they are authorized.