diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..f64f6b38 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,47 @@ +name: Rust testing and building + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + lint: + runs-on: ubuntu-latest + strategy: + matrix: + component: [near] + check: [clippy, fmt] + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.79.0 + components: clippy, rustfmt + target: wasm32-unknown-unknown + override: true + + - name: Run ${{ matrix.check }} + run: | + make ${{ matrix.check }}-${{ matrix.component }} + + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.79.0 + target: wasm32-unknown-unknown + override: true + + - name: Build Near contract + run: | + make rust-build-near diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..dea559ca --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +.PHONY: rust-lint rust-lint-near rust-lint-omni-relayer + +LINT_OPTIONS = -D warnings -D clippy::pedantic -A clippy::missing_errors_doc -A clippy::must_use_candidate -A clippy::module_name_repetitions +RUSTFLAGS = -C link-arg=-s + +NEAR_MANIFEST = ./near/Cargo.toml +OMNI_RELAYER_MANIFEST = ./omni-relayer/Cargo.toml + +clippy: clippy-near #clippy-relayer + +clippy-near: + cargo clippy --manifest-path $(NEAR_MANIFEST) -- $(LINT_OPTIONS) + +fmt-near: + cargo fmt --all --check --manifest-path $(NEAR_MANIFEST) + +fmt-omni-relayer: + cargo fmt --all --check --manifest-path $(OMNI_RELAYER_MANIFEST) + +clippy-omni-relayer: + cargo clippy --manifest-path $(OMNI_RELAYER_MANIFEST) -- $(LINT_OPTIONS) + +rust-build-near: + RUSTFLAGS='$(RUSTFLAGS)' cargo build --target wasm32-unknown-unknown --release --manifest-path $(NEAR_MANIFEST) diff --git a/evm/bridge-token-factory/contracts/BridgeTokenFactory.sol b/evm/bridge-token-factory/contracts/BridgeTokenFactory.sol index 999b4248..c92a415a 100644 --- a/evm/bridge-token-factory/contracts/BridgeTokenFactory.sol +++ b/evm/bridge-token-factory/contracts/BridgeTokenFactory.sol @@ -9,30 +9,16 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "./BridgeToken.sol"; import "./SelectivePausableUpgradable.sol"; import "./Borsh.sol"; +import "./BridgeTypes.sol"; contract BridgeTokenFactory is UUPSUpgradeable, AccessControlUpgradeable, SelectivePausableUpgradable { - enum WhitelistMode { - NotInitialized, - Blocked, - CheckToken, - CheckAccountAndToken - } - - // We removed ProofConsumer from the list of parent contracts and added this gap - // to preserve storage layout when upgrading to the new contract version. - uint256[54] private __gap; - - mapping(address => string) private _ethToNearToken; - mapping(string => address) private _nearToEthToken; - mapping(address => bool) private _isBridgeToken; - - mapping(string => WhitelistMode) private _whitelistedTokens; - mapping(bytes => bool) private _whitelistedAccounts; - bool private _isWhitelistModeEnabled; + mapping(address => string) public ethToNearToken; + mapping(string => address) public nearToEthToken; + mapping(address => bool) public isBridgeToken; address public tokenImplementationAddress; address public nearBridgeDerivedAddress; @@ -47,63 +33,6 @@ contract BridgeTokenFactory is uint constant PAUSED_INIT_TRANSFER = 1 << 0; uint constant PAUSED_FIN_TRANSFER = 1 << 1; - struct FinTransferPayload { - uint128 nonce; - string token; - uint128 amount; - address recipient; - string feeRecipient; - } - - struct MetadataPayload { - string token; - string name; - string symbol; - uint8 decimals; - } - - struct ClaimFeePayload { - uint128[] nonces; - uint128 amount; - address recipient; - } - - event InitTransfer( - address indexed sender, - address indexed tokenAddress, - uint128 indexed nonce, - string token, - uint128 amount, - uint128 fee, - uint128 nativeFee, - string recipient - ); - - - event FinTransfer( - uint128 indexed nonce, - string token, - uint128 amount, - address recipient, - string feeRecipient - ); - - event DeployToken( - address indexed tokenAddress, - string token, - string name, - string symbol, - uint8 decimals - ); - - event SetMetadata( - address indexed tokenAddress, - string token, - string name, - string symbol, - uint8 decimals - ); - error InvalidSignature(); error NonceAlreadyUsed(uint256 nonce); error InvalidFee(); @@ -124,22 +53,9 @@ contract BridgeTokenFactory is _grantRole(PAUSABLE_ADMIN_ROLE, _msgSender()); } - function isBridgeToken(address token) external view returns (bool) { - return _isBridgeToken[token]; - } - - function ethToNearToken(address token) external view returns (string memory) { - require(_isBridgeToken[token], "ERR_NOT_BRIDGE_TOKEN"); - return _ethToNearToken[token]; - } - - function nearToEthToken(string calldata nearTokenId) external view returns (address) { - require(_isBridgeToken[_nearToEthToken[nearTokenId]], "ERR_NOT_BRIDGE_TOKEN"); - return _nearToEthToken[nearTokenId]; - } - - function deployToken(bytes calldata signatureData, MetadataPayload calldata metadata) payable external returns (address) { + function deployToken(bytes calldata signatureData, BridgeTypes.MetadataPayload calldata metadata) payable external returns (address) { bytes memory borshEncoded = bytes.concat( + bytes1(uint8(BridgeTypes.PayloadType.Metadata)), Borsh.encodeString(metadata.token), Borsh.encodeString(metadata.name), Borsh.encodeString(metadata.symbol), @@ -151,7 +67,7 @@ contract BridgeTokenFactory is revert InvalidSignature(); } - require(!_isBridgeToken[_nearToEthToken[metadata.token]], "ERR_TOKEN_EXIST"); + require(!isBridgeToken[nearToEthToken[metadata.token]], "ERR_TOKEN_EXIST"); address bridgeTokenProxy = address( new ERC1967Proxy( @@ -167,7 +83,7 @@ contract BridgeTokenFactory is deployTokenExtension(metadata.token, bridgeTokenProxy); - emit DeployToken( + emit BridgeTypes.DeployToken( bridgeTokenProxy, metadata.token, metadata.name, @@ -175,9 +91,9 @@ contract BridgeTokenFactory is metadata.decimals ); - _isBridgeToken[address(bridgeTokenProxy)] = true; - _ethToNearToken[address(bridgeTokenProxy)] = metadata.token; - _nearToEthToken[metadata.token] = address(bridgeTokenProxy); + isBridgeToken[address(bridgeTokenProxy)] = true; + ethToNearToken[address(bridgeTokenProxy)] = metadata.token; + nearToEthToken[metadata.token] = address(bridgeTokenProxy); return bridgeTokenProxy; } @@ -189,13 +105,13 @@ contract BridgeTokenFactory is string calldata name, string calldata symbol ) external onlyRole(DEFAULT_ADMIN_ROLE) { - require(_isBridgeToken[_nearToEthToken[token]], "ERR_NOT_BRIDGE_TOKEN"); + require(isBridgeToken[nearToEthToken[token]], "ERR_NOT_BRIDGE_TOKEN"); - BridgeToken bridgeToken = BridgeToken(_nearToEthToken[token]); + BridgeToken bridgeToken = BridgeToken(nearToEthToken[token]); bridgeToken.setMetadata(name, symbol, bridgeToken.decimals()); - emit SetMetadata( + emit BridgeTypes.SetMetadata( address(bridgeToken), token, name, @@ -204,12 +120,13 @@ contract BridgeTokenFactory is ); } - function finTransfer(bytes calldata signatureData, FinTransferPayload calldata payload) payable external whenNotPaused(PAUSED_FIN_TRANSFER) { + function finTransfer(bytes calldata signatureData, BridgeTypes.FinTransferPayload calldata payload) payable external whenNotPaused(PAUSED_FIN_TRANSFER) { if (completedTransfers[payload.nonce]) { revert NonceAlreadyUsed(payload.nonce); } bytes memory borshEncoded = bytes.concat( + bytes1(uint8(BridgeTypes.PayloadType.TransferMessage)), Borsh.encodeUint128(payload.nonce), Borsh.encodeString(payload.token), Borsh.encodeUint128(payload.amount), @@ -225,14 +142,14 @@ contract BridgeTokenFactory is revert InvalidSignature(); } - require(_isBridgeToken[_nearToEthToken[payload.token]], "ERR_NOT_BRIDGE_TOKEN"); - BridgeToken(_nearToEthToken[payload.token]).mint(payload.recipient, payload.amount); + require(isBridgeToken[nearToEthToken[payload.token]], "ERR_NOT_BRIDGE_TOKEN"); + BridgeToken(nearToEthToken[payload.token]).mint(payload.recipient, payload.amount); completedTransfers[payload.nonce] = true; finTransferExtension(payload); - emit FinTransfer( + emit BridgeTypes.FinTransfer( payload.nonce, payload.token, payload.amount, @@ -241,7 +158,7 @@ contract BridgeTokenFactory is ); } - function finTransferExtension(FinTransferPayload memory payload) internal virtual {} + function finTransferExtension(BridgeTypes.FinTransferPayload memory payload) internal virtual {} function initTransfer( string calldata token, @@ -251,23 +168,22 @@ contract BridgeTokenFactory is string calldata recipient ) payable external whenNotPaused(PAUSED_INIT_TRANSFER) { initTransferNonce += 1; - _checkWhitelistedToken(token, msg.sender); - require(_isBridgeToken[_nearToEthToken[token]], "ERR_NOT_BRIDGE_TOKEN"); + require(isBridgeToken[nearToEthToken[token]], "ERR_NOT_BRIDGE_TOKEN"); if (fee >= amount) { revert InvalidFee(); } - address tokenAddress = _nearToEthToken[token]; + address tokenAddress = nearToEthToken[token]; BridgeToken(tokenAddress).burn(msg.sender, amount); uint256 extensionValue = msg.value - nativeFee; initTransferExtension(initTransferNonce, token, amount, fee, nativeFee, recipient, msg.sender, extensionValue); - emit InitTransfer(msg.sender, tokenAddress, initTransferNonce, token , amount, fee, nativeFee, recipient); + emit BridgeTypes.InitTransfer(msg.sender, tokenAddress, initTransferNonce, token , amount, fee, nativeFee, recipient); } - function claimNativeFee(bytes calldata signatureData, ClaimFeePayload memory payload) external { + function claimNativeFee(bytes calldata signatureData, BridgeTypes.ClaimFeePayload memory payload) external { bytes memory borshEncodedNonces = Borsh.encodeUint32(uint32(payload.nonces.length)); for (uint i = 0; i < payload.nonces.length; ++i) { @@ -278,6 +194,7 @@ contract BridgeTokenFactory is claimedFee[nonce] = true; borshEncodedNonces = bytes.concat( + bytes1(uint8(BridgeTypes.PayloadType.ClaimNativeFee)), borshEncodedNonces, Borsh.encodeUint128(nonce) ); @@ -314,98 +231,20 @@ contract BridgeTokenFactory is _pause(flags); } - function pauseFinTransfer() external onlyRole(PAUSABLE_ADMIN_ROLE) { - _pause(pausedFlags() | PAUSED_FIN_TRANSFER); - } - - function pauseInitTransfer() external onlyRole(PAUSABLE_ADMIN_ROLE) { - _pause(pausedFlags() | PAUSED_INIT_TRANSFER); - } - function pauseAll() external onlyRole(PAUSABLE_ADMIN_ROLE) { uint flags = PAUSED_FIN_TRANSFER | PAUSED_INIT_TRANSFER; _pause(flags); } - function isWhitelistModeEnabled() external view returns (bool) { - return _isWhitelistModeEnabled; - } - - function getTokenWhitelistMode( - string calldata token - ) external view returns (WhitelistMode) { - return _whitelistedTokens[token]; - } - - function isAccountWhitelistedForToken( - string calldata token, - address account - ) external view returns (bool) { - return _whitelistedAccounts[abi.encodePacked(token, account)]; - } - function upgradeToken( string calldata nearTokenId, address implementation ) external onlyRole(DEFAULT_ADMIN_ROLE) { - require(_isBridgeToken[_nearToEthToken[nearTokenId]], "ERR_NOT_BRIDGE_TOKEN"); - BridgeToken proxy = BridgeToken(payable(_nearToEthToken[nearTokenId])); + require(isBridgeToken[nearToEthToken[nearTokenId]], "ERR_NOT_BRIDGE_TOKEN"); + BridgeToken proxy = BridgeToken(payable(nearToEthToken[nearTokenId])); proxy.upgradeToAndCall(implementation, bytes("")); } - function enableWhitelistMode() external onlyRole(DEFAULT_ADMIN_ROLE) { - _isWhitelistModeEnabled = true; - } - - function disableWhitelistMode() external onlyRole(DEFAULT_ADMIN_ROLE) { - _isWhitelistModeEnabled = false; - } - - function setTokenWhitelistMode( - string calldata token, - WhitelistMode mode - ) external onlyRole(DEFAULT_ADMIN_ROLE) { - _whitelistedTokens[token] = mode; - } - - function addAccountToWhitelist( - string calldata token, - address account - ) external onlyRole(DEFAULT_ADMIN_ROLE) { - require( - _whitelistedTokens[token] != WhitelistMode.NotInitialized, - "ERR_NOT_INITIALIZED_WHITELIST_TOKEN" - ); - _whitelistedAccounts[abi.encodePacked(token, account)] = true; - } - - function removeAccountFromWhitelist( - string calldata token, - address account - ) external onlyRole(DEFAULT_ADMIN_ROLE) { - delete _whitelistedAccounts[abi.encodePacked(token, account)]; - } - - function _checkWhitelistedToken(string memory token, address account) internal view { - if (!_isWhitelistModeEnabled) { - return; - } - - WhitelistMode tokenMode = _whitelistedTokens[token]; - require( - tokenMode != WhitelistMode.NotInitialized, - "ERR_NOT_INITIALIZED_WHITELIST_TOKEN" - ); - require(tokenMode != WhitelistMode.Blocked, "ERR_WHITELIST_TOKEN_BLOCKED"); - - if (tokenMode == WhitelistMode.CheckAccountAndToken) { - require( - _whitelistedAccounts[abi.encodePacked(token, account)], - "ERR_ACCOUNT_NOT_IN_WHITELIST" - ); - } - } - function _authorizeUpgrade( address newImplementation ) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} diff --git a/evm/bridge-token-factory/contracts/BridgeTokenFactoryWormhole.sol b/evm/bridge-token-factory/contracts/BridgeTokenFactoryWormhole.sol index ab7fa502..08d2bb67 100644 --- a/evm/bridge-token-factory/contracts/BridgeTokenFactoryWormhole.sol +++ b/evm/bridge-token-factory/contracts/BridgeTokenFactoryWormhole.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.24; import {BridgeTokenFactory} from "./BridgeTokenFactory.sol"; import "./Borsh.sol"; +import "./BridgeTypes.sol"; interface IWormhole { function publishMessage( @@ -46,7 +47,7 @@ contract BridgeTokenFactoryWormhole is BridgeTokenFactory { wormholeNonce++; } - function finTransferExtension(FinTransferPayload memory payload) internal override { + function finTransferExtension(BridgeTypes.FinTransferPayload memory payload) internal override { _wormhole.publishMessage{value: msg.value}( wormholeNonce, abi.encode(MessageType.FinTransfer, payload.token, payload.amount, payload.feeRecipient, payload.nonce), diff --git a/evm/bridge-token-factory/contracts/BridgeTypes.sol b/evm/bridge-token-factory/contracts/BridgeTypes.sol new file mode 100644 index 00000000..b805546c --- /dev/null +++ b/evm/bridge-token-factory/contracts/BridgeTypes.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.24; + +library BridgeTypes { + struct FinTransferPayload { + uint128 nonce; + string token; + uint128 amount; + address recipient; + string feeRecipient; + } + + struct MetadataPayload { + string token; + string name; + string symbol; + uint8 decimals; + } + + struct ClaimFeePayload { + uint128[] nonces; + uint128 amount; + address recipient; + } + + event InitTransfer( + address indexed sender, + address indexed tokenAddress, + uint128 indexed nonce, + string token, + uint128 amount, + uint128 fee, + uint128 nativeFee, + string recipient + ); + + event FinTransfer( + uint128 indexed nonce, + string token, + uint128 amount, + address recipient, + string feeRecipient + ); + + event DeployToken( + address indexed tokenAddress, + string token, + string name, + string symbol, + uint8 decimals + ); + + event SetMetadata( + address indexed tokenAddress, + string token, + string name, + string symbol, + uint8 decimals + ); + + enum PayloadType { + TransferMessage, + Metadata, + ClaimNativeFee + } +} diff --git a/evm/bridge-token-factory/test/BridgeToken.js b/evm/bridge-token-factory/test/BridgeToken.js index 0214f06a..4463b353 100644 --- a/evm/bridge-token-factory/test/BridgeToken.js +++ b/evm/bridge-token-factory/test/BridgeToken.js @@ -12,9 +12,10 @@ const WhitelistMode = { const PauseMode = { UnpausedAll: 0, - PausedInitTransfer: 1, - PausedFinTransfer: 2, + PausedInitTransfer: 1 << 0, + PausedFinTransfer: 1 << 1, } +const PauseAll = PauseMode.PausedInitTransfer | PauseMode.PausedFinTransfer; describe('BridgeToken', () => { const wrappedNearId = 'wrap.testnet' @@ -131,7 +132,7 @@ describe('BridgeToken', () => { await createToken(wrappedNearId); await expect ( - BridgeTokenFactory.pauseFinTransfer() + BridgeTokenFactory.pause(PauseMode.PausedFinTransfer) ) .to .emit(BridgeTokenFactory, 'Paused') @@ -223,22 +224,19 @@ describe('BridgeToken', () => { it('withdraw token', async function () { const { token } = await createToken(wrappedNearId); - await BridgeTokenFactory.setTokenWhitelistMode(wrappedNearId, WhitelistMode.CheckToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(wrappedNearId) - ).to.be.equal(WhitelistMode.CheckToken); - const { signature, payload } = depositSignature(wrappedNearId, user1.address); await BridgeTokenFactory.finTransfer(signature, payload); const recipient = 'testrecipient.near'; const fee = 0; + const nativeFee = 0; await expect( BridgeTokenFactory.connect(user1).initTransfer( wrappedNearId, payload.amount, fee, + nativeFee, recipient ) ) @@ -251,6 +249,7 @@ describe('BridgeToken', () => { wrappedNearId, payload.amount, fee, + nativeFee, recipient, ); @@ -260,23 +259,19 @@ describe('BridgeToken', () => { it('cant withdraw token when paused', async function () { await createToken(wrappedNearId); - await BridgeTokenFactory.setTokenWhitelistMode(wrappedNearId, WhitelistMode.CheckToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(wrappedNearId) - ).to.be.equal(WhitelistMode.CheckToken); - const { signature, payload } = depositSignature(wrappedNearId, user1.address); await BridgeTokenFactory.finTransfer(signature, payload); const fee = 0; + const nativeFee = 0; await expect( - BridgeTokenFactory.pauseInitTransfer() + BridgeTokenFactory.pause(PauseMode.PausedInitTransfer) ) .to .emit(BridgeTokenFactory, 'Paused') .withArgs(adminAccount.address, PauseMode.PausedInitTransfer); await expect( - BridgeTokenFactory.initTransfer(wrappedNearId, payload.amount, fee, 'testrecipient.near') + BridgeTokenFactory.initTransfer(wrappedNearId, payload.amount, fee, nativeFee, 'testrecipient.near') ) .to .be @@ -286,16 +281,11 @@ describe('BridgeToken', () => { it('can deposit and withdraw after unpausing', async function () { const { token } = await createToken(wrappedNearId); - await BridgeTokenFactory.setTokenWhitelistMode(wrappedNearId, WhitelistMode.CheckToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(wrappedNearId) - ).to.be.equal(WhitelistMode.CheckToken); - const { signature, payload } = depositSignature(wrappedNearId, user1.address); await BridgeTokenFactory.finTransfer(signature, payload); await expect( - BridgeTokenFactory.pauseInitTransfer() + BridgeTokenFactory.pause(PauseMode.PausedInitTransfer) ) .to .emit(BridgeTokenFactory, 'Paused') @@ -310,10 +300,12 @@ describe('BridgeToken', () => { const recipient = 'testrecipient.near'; const fee = 0; + const nativeFee = 0; await BridgeTokenFactory.connect(user1).initTransfer( wrappedNearId, payload.amount, fee, + nativeFee, recipient ); @@ -349,7 +341,7 @@ describe('BridgeToken', () => { it('Test selective pause', async function () { // Pause withdraw await expect( - BridgeTokenFactory.pauseInitTransfer() + BridgeTokenFactory.pause(PauseMode.PausedInitTransfer) ) .to .emit(BridgeTokenFactory, 'Paused') @@ -358,7 +350,7 @@ describe('BridgeToken', () => { // Pause withdraw again await expect( - BridgeTokenFactory.pauseInitTransfer() + BridgeTokenFactory.pause(PauseMode.PausedInitTransfer) ) .to .emit(BridgeTokenFactory, 'Paused') @@ -369,7 +361,7 @@ describe('BridgeToken', () => { // Pause deposit await expect( - BridgeTokenFactory.pauseFinTransfer() + BridgeTokenFactory.pause(PauseMode.PausedFinTransfer | PauseMode.PausedInitTransfer) ) .to .emit(BridgeTokenFactory, 'Paused') @@ -378,7 +370,7 @@ describe('BridgeToken', () => { // Pause deposit again await expect( - BridgeTokenFactory.pauseFinTransfer() + BridgeTokenFactory.pause(PauseMode.PausedFinTransfer | PauseMode.PausedInitTransfer) ) .to .emit(BridgeTokenFactory, 'Paused') @@ -427,24 +419,19 @@ describe('BridgeToken', () => { }) it("Test grant admin role", async function() { - await BridgeTokenFactory.connect(adminAccount).disableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.false; + await BridgeTokenFactory.connect(adminAccount).pause(PauseMode.UnpausedAll); + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.false; - await BridgeTokenFactory.connect(adminAccount).enableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; + await BridgeTokenFactory.connect(adminAccount).pauseAll(); + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.true; const signers = await ethers.getSigners(); const newAdminAccount = signers[2]; const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000"; await expect( - BridgeTokenFactory.connect(newAdminAccount).disableWhitelistMode() + BridgeTokenFactory.connect(newAdminAccount).pause(PauseMode.UnpausedAll) ).to.be.revertedWithCustomError(BridgeTokenFactory, 'AccessControlUnauthorizedAccount'); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; - - await expect( - BridgeTokenFactory.connect(newAdminAccount).enableWhitelistMode() - ).to.be.revertedWithCustomError(BridgeTokenFactory, 'AccessControlUnauthorizedAccount'); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.true; // Grant DEFAULT_ADMIN_ROLE to newAdminAccount await expect( @@ -457,11 +444,11 @@ describe('BridgeToken', () => { newAdminAccount.address, adminAccount.address ); - await BridgeTokenFactory.connect(newAdminAccount).disableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.false; + await BridgeTokenFactory.connect(newAdminAccount).pause(PauseMode.UnpausedAll); + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.false; - await BridgeTokenFactory.connect(newAdminAccount).enableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; + await BridgeTokenFactory.connect(newAdminAccount).pause(PauseAll); + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.true; // Revoke DEFAULT_ADMIN_ROLE from adminAccount await expect( @@ -482,20 +469,15 @@ describe('BridgeToken', () => { // Check tx reverted on call from revoked adminAccount await expect( - BridgeTokenFactory.connect(adminAccount).disableWhitelistMode() - ).to.be.revertedWithCustomError(BridgeTokenFactory, 'AccessControlUnauthorizedAccount'); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; - - await expect( - BridgeTokenFactory.connect(adminAccount).enableWhitelistMode() + BridgeTokenFactory.connect(adminAccount).pause(PauseMode.UnpausedAll) ).to.be.revertedWithCustomError(BridgeTokenFactory, 'AccessControlUnauthorizedAccount'); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.true; // Check newAdminAccount can perform admin calls - await BridgeTokenFactory.connect(newAdminAccount).disableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.false; - await BridgeTokenFactory.connect(newAdminAccount).enableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; + await BridgeTokenFactory.connect(newAdminAccount).pause(PauseMode.UnpausedAll); + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.false; + await BridgeTokenFactory.connect(newAdminAccount).pause(PauseAll); + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.true; // Check newAdminAccount can grant DEFAULT_ADMIN_ROLE to adminAccount await expect( @@ -512,321 +494,9 @@ describe('BridgeToken', () => { ); // Check that adminAccount can perform admin calls again - await BridgeTokenFactory.connect(adminAccount).disableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.false; - await BridgeTokenFactory.connect(adminAccount).enableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; - }); - - describe("Whitelist", function() { - beforeEach(async function() { - await BridgeTokenFactory.enableWhitelistMode() - }); - - it("Test account in whitelist", async function() { - const tokenInfo = await createToken(wrappedNearId); - - const { signature, payload } = depositSignature(wrappedNearId, user1.address); - await BridgeTokenFactory.finTransfer(signature, payload); - - const recipient = payload.recipient; - const amountToTransfer = payload.amount; - - await BridgeTokenFactory.setTokenWhitelistMode(wrappedNearId, WhitelistMode.CheckAccountAndToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(wrappedNearId) - ).to.be.equal(WhitelistMode.CheckAccountAndToken); - - await BridgeTokenFactory.addAccountToWhitelist( - wrappedNearId, - user1.address - ); - expect( - await BridgeTokenFactory.isAccountWhitelistedForToken( - wrappedNearId, - user1.address - ) - ).to.be.true; - - const fee = 0; - await BridgeTokenFactory.connect(user1).initTransfer(wrappedNearId, amountToTransfer, fee, recipient); - expect( - (await tokenInfo.token.balanceOf(user1.address)).toString() - ).to.be.equal("0"); - }); - - it("Test token in whitelist", async function() { - const tokenInfo = await createToken(wrappedNearId); - - const { signature, payload } = depositSignature(wrappedNearId, user1.address); - await BridgeTokenFactory.finTransfer(signature, payload); - - const recipient = payload.recipient; - const amountToTransfer = payload.amount; - const fee = 0; - - await BridgeTokenFactory.setTokenWhitelistMode(wrappedNearId, WhitelistMode.CheckToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(wrappedNearId) - ).to.be.equal(WhitelistMode.CheckToken); - - await BridgeTokenFactory.connect(user1).initTransfer(wrappedNearId, amountToTransfer, fee, recipient); - expect( - (await tokenInfo.token.balanceOf(user1.address)).toString() - ).to.be.equal("0"); - }); - - it("Test multiple tokens", async function() { - const whitelistTokens = [ - "wrap.testnet", - "token-bridge-test.testnet", - ]; - const blacklistTokens = [ - "blacklisted1.testnet", - "blacklisted2.testnet", - ]; - - for (token of whitelistTokens) { - await createToken(token); - - const { signature, payload } = depositSignature(token, user1.address); - await BridgeTokenFactory.finTransfer(signature, payload); - - await BridgeTokenFactory.setTokenWhitelistMode(token, WhitelistMode.CheckToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(token) - ).to.be.equal(WhitelistMode.CheckToken); - } - - const amountToWithdraw = 1; - const recipient = "testrecipient.near"; - const fee = 0; - for (token of blacklistTokens) { - await expect( - BridgeTokenFactory.connect(user1).initTransfer( - token, - amountToWithdraw, - fee, - recipient - ) - ).to.be.revertedWith("ERR_NOT_INITIALIZED_WHITELIST_TOKEN"); - } - - let nonce = 0; - for (token of whitelistTokens) { - nonce++; - await expect( - BridgeTokenFactory.connect(user1).initTransfer( - token, - amountToWithdraw, - fee, - recipient - ) - ) - .to - .emit(BridgeTokenFactory, "InitTransfer") - .withArgs( - user1.address, - await BridgeTokenFactory.nearToEthToken(token), - nonce, - token, - amountToWithdraw, - fee, - recipient, - - ); - } - }); - - it("Test multiple accounts", async function() { - const whitelistTokens = [ - "wrap.testnet", - "token-bridge-test.testnet", - ]; - - const whitelistAccounts = [ - user1, - user2 - ]; - - const signers = await ethers.getSigners(); - const blacklistAccounts = signers.slice(0, 2); - - const tokensInfo = []; - for (token of whitelistTokens) { - tokensInfo.push(createToken(token)); - await BridgeTokenFactory.setTokenWhitelistMode(token, WhitelistMode.CheckAccountAndToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(token) - ).to.be.equal(WhitelistMode.CheckAccountAndToken); - - for (const account of whitelistAccounts) { - const { signature, payload } = depositSignature(token, account.address); - await BridgeTokenFactory.finTransfer(signature, payload); - - await BridgeTokenFactory.addAccountToWhitelist( - token, - account.address - ); - expect( - await BridgeTokenFactory.isAccountWhitelistedForToken( - token, - account.address - ) - ).to.be.true; - } - } - - const amountToWithdraw = 1; - const recipient = "testrecipient.near"; - const fee = 0; - let nonce = 0; - for (token of whitelistTokens) { - for (const account of whitelistAccounts) { - nonce++; - await expect( - BridgeTokenFactory.connect(account).initTransfer( - token, - amountToWithdraw, - fee, - recipient - ) - ) - .to - .emit(BridgeTokenFactory, "InitTransfer") - .withArgs( - account.address, - await BridgeTokenFactory.nearToEthToken(token), - nonce, - token, - amountToWithdraw, - fee, - recipient, - ); - } - - for (const account of blacklistAccounts) { - await expect( - BridgeTokenFactory.connect(account).initTransfer( - token, - amountToWithdraw, - fee, - recipient - ) - ).revertedWith("ERR_ACCOUNT_NOT_IN_WHITELIST"); - } - } - }); - - it("Test remove account from whitelist", async function() { - const tokenInfo = await createToken(wrappedNearId); - - const { signature, payload } = depositSignature(wrappedNearId, user2.address); - await BridgeTokenFactory.finTransfer(signature, payload); - - await BridgeTokenFactory.setTokenWhitelistMode(wrappedNearId, WhitelistMode.CheckAccountAndToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(wrappedNearId) - ).to.be.equal(WhitelistMode.CheckAccountAndToken); - - await BridgeTokenFactory.addAccountToWhitelist( - wrappedNearId, - user2.address - ); - expect( - await BridgeTokenFactory.isAccountWhitelistedForToken( - wrappedNearId, - user2.address - ) - ).to.be.true; - - const amountToWithdraw = 10; - const recipient = "testrecipient.near"; - const fee = 0; - - await BridgeTokenFactory.connect(user2).initTransfer(wrappedNearId, amountToWithdraw, fee, recipient); - - await BridgeTokenFactory.removeAccountFromWhitelist(wrappedNearId, user2.address); - expect( - await BridgeTokenFactory.isAccountWhitelistedForToken( - wrappedNearId, - user2.address - ) - ).to.be.false; - - await expect( - BridgeTokenFactory.connect(user2).initTransfer(wrappedNearId, amountToWithdraw, fee, recipient) - ).to.be.revertedWith("ERR_ACCOUNT_NOT_IN_WHITELIST"); - - expect( - (await tokenInfo.token.balanceOf(user2.address)).toString() - ).to.be.equal((payload.amount - amountToWithdraw).toString()); - }); - - it("Test token or account not in whitelist", async function() { - const tokenId = "token-bridge-test.testnet"; - const tokenInfo = await createToken(tokenId); - - const { signature, payload } = depositSignature(tokenId, user2.address); - await BridgeTokenFactory.finTransfer(signature, payload); - - const amountToWithdraw = payload.amount / 2; - const recipient = "testrecipient.near"; - const fee = 0; - - await expect( - BridgeTokenFactory.initTransfer(tokenId, amountToWithdraw, fee, recipient) - ).to.be.revertedWith("ERR_NOT_INITIALIZED_WHITELIST_TOKEN"); - - await BridgeTokenFactory.setTokenWhitelistMode(tokenId, WhitelistMode.Blocked); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(tokenId) - ).to.be.equal(WhitelistMode.Blocked); - - await expect( - BridgeTokenFactory.initTransfer(tokenId, amountToWithdraw, fee, recipient) - ).to.be.revertedWith("ERR_WHITELIST_TOKEN_BLOCKED"); - - await BridgeTokenFactory.setTokenWhitelistMode(tokenId, WhitelistMode.CheckAccountAndToken); - expect( - await BridgeTokenFactory.getTokenWhitelistMode(tokenId) - ).to.be.equal(WhitelistMode.CheckAccountAndToken); - - await expect( - BridgeTokenFactory.initTransfer(tokenId, amountToWithdraw, fee, recipient) - ).to.be.revertedWith("ERR_ACCOUNT_NOT_IN_WHITELIST"); - - // Disable whitelist mode - await BridgeTokenFactory.disableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.false; - await BridgeTokenFactory.connect(user2).initTransfer(tokenId, amountToWithdraw, fee, recipient); - expect( - (await tokenInfo.token.balanceOf(user2.address)).toString() - ).to.be.equal(amountToWithdraw.toString()); - - // Enable whitelist mode - await BridgeTokenFactory.enableWhitelistMode(); - expect(await BridgeTokenFactory.isWhitelistModeEnabled()).to.be.true; - await expect( - BridgeTokenFactory.initTransfer(tokenId, amountToWithdraw, fee, recipient) - ).to.be.revertedWith("ERR_ACCOUNT_NOT_IN_WHITELIST"); - - await BridgeTokenFactory.addAccountToWhitelist( - tokenId, - user2.address - ); - expect( - await BridgeTokenFactory.isAccountWhitelistedForToken( - tokenId, - user2.address - ) - ).to.be.true; - - await BridgeTokenFactory.connect(user2).initTransfer(tokenId, amountToWithdraw, fee, recipient); - - expect( - (await tokenInfo.token.balanceOf(user2.address)).toString() - ).to.be.equal("0"); - }); + await BridgeTokenFactory.connect(adminAccount).pause(PauseMode.UnpausedAll); + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.false; + await BridgeTokenFactory.connect(adminAccount).pause(PauseAll); + expect(await BridgeTokenFactory.paused(PauseMode.PausedInitTransfer)).to.be.true; }); }) diff --git a/evm/bridge-token-factory/test/BridgeTokenWormhole.js b/evm/bridge-token-factory/test/BridgeTokenWormhole.js index c9f8e6fc..4be2fbc5 100644 --- a/evm/bridge-token-factory/test/BridgeTokenWormhole.js +++ b/evm/bridge-token-factory/test/BridgeTokenWormhole.js @@ -102,10 +102,11 @@ describe('BridgeTokenWormhole', () => { const recipient = 'testrecipient.near'; const fee = 0; + const nativeFee = 0; const nonce = 1; const expectedPayload = ethers.AbiCoder.defaultAbiCoder().encode( - ["uint8", "uint128", "string", "uint128", "uint128", "string", "address"], - [0, nonce, wrappedNearId, payload.amount, fee, recipient, user1.address] + ["uint8", "uint128", "string", "uint128", "uint128", "uint128", "string", "address"], + [0, nonce, wrappedNearId, payload.amount, fee, nativeFee, recipient, user1.address] ); await expect( @@ -113,6 +114,7 @@ describe('BridgeTokenWormhole', () => { wrappedNearId, payload.amount, fee, + nativeFee, recipient ) ) diff --git a/near/mock/mock-prover/src/lib.rs b/near/mock/mock-prover/src/lib.rs index 8a630cec..5a13c4e0 100644 --- a/near/mock/mock-prover/src/lib.rs +++ b/near/mock/mock-prover/src/lib.rs @@ -24,20 +24,23 @@ impl Default for OmniProver { #[near] impl OmniProver { - pub fn add_prover(&mut self, prover_id: ProverId, account_id: AccountId) { - self.provers.insert(&prover_id, &account_id); + pub fn add_prover(&mut self, prover_id: &ProverId, account_id: &AccountId) { + self.provers.insert(prover_id, account_id); } - pub fn remove_prover(&mut self, prover_id: ProverId) { - self.provers.remove(&prover_id); + pub fn remove_prover(&mut self, prover_id: &ProverId) { + self.provers.remove(prover_id); } pub fn get_provers(&self) -> Vec<(ProverId, AccountId)> { self.provers.iter().collect::>() } + /// # Panics + /// + /// This function will panic if the prover args are not valid. #[result_serializer(borsh)] - pub fn verify_proof(&self, #[serializer(borsh)] args: VerifyProofArgs) -> ProverResult { + pub fn verify_proof(&self, #[serializer(borsh)] args: &VerifyProofArgs) -> ProverResult { ProverResult::try_from_slice(&args.prover_args).unwrap() } } diff --git a/near/mock/mock-token/src/lib.rs b/near/mock/mock-token/src/lib.rs index bf0919a6..9d24a28e 100644 --- a/near/mock/mock-token/src/lib.rs +++ b/near/mock/mock-token/src/lib.rs @@ -35,11 +35,11 @@ impl Contract { /// Initializes the contract with the given total supply owned by the given `owner_id` with /// default metadata (for example purposes only). #[init] - pub fn new_default_meta(owner_id: AccountId, total_supply: U128) -> Self { + pub fn new_default_meta(owner_id: &AccountId, total_supply: U128) -> Self { Self::new( owner_id, total_supply, - FungibleTokenMetadata { + &FungibleTokenMetadata { spec: FT_METADATA_SPEC.to_string(), name: "Example NEAR fungible token".to_string(), symbol: "EXAMPLE".to_string(), @@ -54,18 +54,18 @@ impl Contract { /// Initializes the contract with the given total supply owned by the given `owner_id` with /// the given fungible token metadata. #[init] - pub fn new(owner_id: AccountId, total_supply: U128, metadata: FungibleTokenMetadata) -> Self { + pub fn new(owner_id: &AccountId, total_supply: U128, metadata: &FungibleTokenMetadata) -> Self { require!(!env::state_exists(), "Already initialized"); metadata.assert_valid(); let mut this = Self { token: FungibleToken::new(StorageKey::FungibleToken), - metadata: LazyOption::new(StorageKey::Metadata, Some(&metadata)), + metadata: LazyOption::new(StorageKey::Metadata, Some(metadata)), }; - this.token.internal_register_account(&owner_id); - this.token.internal_deposit(&owner_id, total_supply.into()); + this.token.internal_register_account(owner_id); + this.token.internal_deposit(owner_id, total_supply.into()); near_contract_standards::fungible_token::events::FtMint { - owner_id: &owner_id, + owner_id, amount: total_supply, memo: Some("new tokens are minted"), } @@ -79,7 +79,7 @@ impl Contract { impl FungibleTokenCore for Contract { #[payable] fn ft_transfer(&mut self, receiver_id: AccountId, amount: U128, memo: Option) { - self.token.ft_transfer(receiver_id, amount, memo) + self.token.ft_transfer(receiver_id, amount, memo); } #[payable] diff --git a/near/nep141-locker/src/lib.rs b/near/nep141-locker/src/lib.rs index c68a3e0a..f5148b1d 100644 --- a/near/nep141-locker/src/lib.rs +++ b/near/nep141-locker/src/lib.rs @@ -22,7 +22,7 @@ use omni_types::prover_args::VerifyProofArgs; use omni_types::prover_result::ProverResult; use omni_types::{ ChainKind, ClaimNativeFeePayload, Fee, InitTransferMsg, MetadataPayload, NativeFee, - NearRecipient, Nonce, OmniAddress, SignRequest, TransferId, TransferMessage, + NearRecipient, Nonce, OmniAddress, PayloadType, SignRequest, TransferId, TransferMessage, TransferMessagePayload, UpdateFee, }; use storage::{TransferMessageStorage, TransferMessageStorageValue}; @@ -31,7 +31,7 @@ mod errors; mod storage; const LOG_METADATA_GAS: Gas = Gas::from_tgas(10); -const LOG_METADATA_CALLBCAK_GAS: Gas = Gas::from_tgas(260); +const LOG_METADATA_CALLBACK_GAS: Gas = Gas::from_tgas(260); const MPC_SIGNING_GAS: Gas = Gas::from_tgas(250); const SIGN_TRANSFER_CALLBACK_GAS: Gas = Gas::from_tgas(5); const SIGN_LOG_METADATA_CALLBACK_GAS: Gas = Gas::from_tgas(5); @@ -39,13 +39,15 @@ const SIGN_CLAIM_NATIVE_FEE_CALLBACK_GAS: Gas = Gas::from_tgas(5); const VERIFY_POOF_GAS: Gas = Gas::from_tgas(50); const CLAIM_FEE_CALLBACK_GAS: Gas = Gas::from_tgas(50); const BIND_TOKEN_CALLBACK_GAS: Gas = Gas::from_tgas(25); +const BIND_TOKEN_REFUND_GAS: Gas = Gas::from_tgas(5); const FT_TRANSFER_CALL_GAS: Gas = Gas::from_tgas(50); const FT_TRANSFER_GAS: Gas = Gas::from_tgas(5); +const WNEAR_WITHDRAW_GAS: Gas = Gas::from_tgas(10); const STORAGE_BALANCE_OF_GAS: Gas = Gas::from_tgas(3); const STORAGE_DEPOSIT_GAS: Gas = Gas::from_tgas(3); const NO_DEPOSIT: NearToken = NearToken::from_near(0); const ONE_YOCTO: NearToken = NearToken::from_yoctonear(1); -const NEP141_DEPOSIT: NearToken = NearToken::from_yoctonear(1250000000000000000000); +const NEP141_DEPOSIT: NearToken = NearToken::from_yoctonear(1_250_000_000_000_000_000_000); const SIGN_PATH: &str = "bridge-1"; @@ -107,6 +109,11 @@ pub trait Prover { fn verify_proof(&self, #[serializer(borsh)] args: VerifyProofArgs) -> ProverResult; } +#[ext_contract(ext_wnear_token)] +pub trait ExtWNearToken { + fn near_withdraw(&self, amount: U128); +} + #[near(contract_state)] #[derive(Pausable, Upgradable, PanicOnDefault)] #[access_control(role_type(Role))] @@ -127,6 +134,7 @@ pub struct Contract { pub mpc_signer: AccountId, pub current_nonce: Nonce, pub accounts_balances: LookupMap, + pub wnear_account_id: AccountId, } #[near] @@ -179,7 +187,12 @@ impl FungibleTokenReceiver for Contract { #[near] impl Contract { #[init] - pub fn new(prover_account: AccountId, mpc_signer: AccountId, nonce: U128) -> Self { + pub fn new( + prover_account: AccountId, + mpc_signer: AccountId, + nonce: U128, + wnear_account_id: AccountId, + ) -> Self { let mut contract = Self { prover_account, factories: LookupMap::new(StorageKey::Factories), @@ -189,6 +202,7 @@ impl Contract { mpc_signer, current_nonce: nonce.0, accounts_balances: LookupMap::new(StorageKey::AccountsBalances), + wnear_account_id, }; contract.acl_init_super_admin(near_sdk::env::predecessor_account_id()); @@ -196,26 +210,27 @@ impl Contract { contract } - pub fn log_metadata(&self, token_id: AccountId) -> Promise { + pub fn log_metadata(&self, token_id: &AccountId) -> Promise { ext_token::ext(token_id.clone()) .with_static_gas(LOG_METADATA_GAS) .ft_metadata() .then( Self::ext(env::current_account_id()) - .with_static_gas(LOG_METADATA_CALLBCAK_GAS) + .with_static_gas(LOG_METADATA_CALLBACK_GAS) .with_attached_deposit(env::attached_deposit()) - .log_metadata_callbcak(token_id), + .log_metadata_callback(token_id), ) } #[private] #[result_serializer(borsh)] - pub fn log_metadata_callbcak( + pub fn log_metadata_callback( &self, #[callback] metadata: FungibleTokenMetadata, - token_id: AccountId, + token_id: &AccountId, ) -> Promise { let metadata_payload = MetadataPayload { + prefix: PayloadType::Metadata, token: token_id.to_string(), name: metadata.name, symbol: metadata.symbol, @@ -237,13 +252,13 @@ impl Contract { .then( Self::ext(env::current_account_id()) .with_static_gas(SIGN_LOG_METADATA_CALLBACK_GAS) - .sign_log_metadata_callbcak(metadata_payload), + .sign_log_metadata_callback(metadata_payload), ) } #[private] #[result_serializer(borsh)] - pub fn sign_log_metadata_callbcak( + pub fn sign_log_metadata_callback( &self, #[callback_result] call_result: Result, #[serializer(borsh)] metadata_payload: MetadataPayload, @@ -277,10 +292,10 @@ impl Contract { "ERR_INVALID_FEE" ); - let diff_native_fee = current_fee + let diff_native_fee = fee .native_fee .0 - .checked_sub(fee.native_fee.0) + .checked_sub(current_fee.native_fee.0) .sdk_expect("ERR_LOWER_FEE"); require!( @@ -318,6 +333,7 @@ impl Contract { } let claim_payload = ClaimNativeFeePayload { + prefix: PayloadType::ClaimNativeFee, nonces, amount: U128(amount), recipient, @@ -357,12 +373,18 @@ impl Contract { } } + /// # Panics + /// + /// This function will panic under the following conditions: + /// + /// - If the `borsh::to_vec` serialization of the `TransferMessagePayload` fails. + /// - If a `fee` is provided and it doesn't match the fee in the stored transfer message. #[payable] pub fn sign_transfer( &mut self, nonce: U128, fee_recipient: Option, - fee: Option, + fee: &Option, ) -> Promise { let transfer_message = self.get_transfer_message(nonce); if let Some(fee) = &fee { @@ -370,6 +392,7 @@ impl Contract { } let transfer_payload = TransferMessagePayload { + prefix: PayloadType::TransferMessage, nonce, token: transfer_message.token, amount: U128(transfer_message.amount.0 - transfer_message.fee.fee.0), @@ -390,7 +413,7 @@ impl Contract { .then( Self::ext(env::current_account_id()) .with_static_gas(SIGN_TRANSFER_CALLBACK_GAS) - .sign_transfer_callback(transfer_payload, transfer_message.fee), + .sign_transfer_callback(transfer_payload, &transfer_message.fee), ) } @@ -399,7 +422,7 @@ impl Contract { &mut self, #[callback_result] call_result: Result, #[serializer(borsh)] message_payload: TransferMessagePayload, - #[serializer(borsh)] fee: Fee, + #[serializer(borsh)] fee: &Fee, ) { if let Ok(signature) = call_result { let nonce = message_payload.nonce; @@ -442,20 +465,22 @@ impl Contract { .with_attached_deposit(attached_deposit) .with_static_gas(CLAIM_FEE_CALLBACK_GAS) .fin_transfer_callback( - args.storage_deposit_args, + &args.storage_deposit_args, env::predecessor_account_id(), args.native_fee_recipient, ), ) } + // TODO: try to split this function + #[allow(clippy::too_many_lines)] #[private] #[payable] pub fn fin_transfer_callback( &mut self, - #[serializer(borsh)] storage_deposit_args: StorageDepositArgs, + #[serializer(borsh)] storage_deposit_args: &StorageDepositArgs, #[serializer(borsh)] predecessor_account_id: AccountId, - #[serializer(borsh)] native_fee_recipient: OmniAddress, + #[serializer(borsh)] native_fee_recipient: Option, ) -> PromiseOrValue { let Ok(ProverResult::InitTransfer(init_transfer)) = Self::decode_prover_result(0) else { env::panic_str("Invalid proof message") @@ -471,13 +496,22 @@ impl Contract { let mut required_balance; if let OmniAddress::Near(recipient) = &transfer_message.recipient { - required_balance = self.add_fin_transfer( - &transfer_message.get_transfer_id(), - &Some(NativeFee { + let native_fee = if transfer_message.fee.native_fee.0 != 0 { + let recipient = native_fee_recipient.sdk_expect("ERR_FEE_RECIPIENT_NOT_SET"); + require!( + transfer_message.get_origin_chain() == recipient.get_chain(), + "ERR_WRONG_FEE_RECIPIENT_CHAIN" + ); + Some(NativeFee { amount: transfer_message.fee.native_fee, - recipient: native_fee_recipient, - }), - ); + recipient, + }) + } else { + None + }; + + required_balance = + self.add_fin_transfer(&transfer_message.get_transfer_id(), &native_fee); let recipient: NearRecipient = recipient.parse().sdk_expect("Failed to parse recipient"); @@ -493,16 +527,31 @@ impl Contract { ); let amount_to_transfer = U128(transfer_message.amount.0 - transfer_message.fee.fee.0); - let mut promise = match recipient.message { - Some(message) => ext_token::ext(transfer_message.token.clone()) - .with_static_gas(FT_TRANSFER_CALL_GAS) - .with_attached_deposit(ONE_YOCTO) - .ft_transfer_call(recipient.target, amount_to_transfer, None, message), - None => ext_token::ext(transfer_message.token.clone()) - .with_static_gas(FT_TRANSFER_GAS) - .with_attached_deposit(ONE_YOCTO) - .ft_transfer(recipient.target, amount_to_transfer, None), - }; + + let mut promise = + if transfer_message.token == self.wnear_account_id && recipient.message.is_none() { + ext_wnear_token::ext(self.wnear_account_id.clone()) + .with_static_gas(WNEAR_WITHDRAW_GAS) + .with_attached_deposit(ONE_YOCTO) + .near_withdraw(amount_to_transfer) + .then( + Promise::new(recipient.target) + .transfer(NearToken::from_yoctonear(amount_to_transfer.0)), + ) + } else { + let transfer = ext_token::ext(transfer_message.token.clone()) + .with_attached_deposit(ONE_YOCTO); + match recipient.message { + Some(message) => transfer + .with_static_gas(FT_TRANSFER_CALL_GAS) + .ft_transfer_call(recipient.target, amount_to_transfer, None, message), + None => transfer.with_static_gas(FT_TRANSFER_GAS).ft_transfer( + recipient.target, + amount_to_transfer, + None, + ), + } + }; if transfer_message.fee.fee.0 > 0 { require!( @@ -520,6 +569,10 @@ impl Contract { None, ), ); + + required_balance = required_balance.saturating_add(NearToken::from_yoctonear(2)); + } else { + required_balance = required_balance.saturating_add(ONE_YOCTO); } self.update_storage_balance( @@ -587,7 +640,7 @@ impl Contract { #[payable] pub fn claim_fee_callback( &mut self, - #[serializer(borsh)] native_fee_recipient: OmniAddress, + #[serializer(borsh)] native_fee_recipient: Option, #[serializer(borsh)] predecessor_account_id: AccountId, #[callback_result] #[serializer(borsh)] @@ -611,8 +664,14 @@ impl Contract { let fee = message.amount.0 - fin_transfer.amount.0; if message.fee.native_fee.0 != 0 { + let native_fee_recipient = native_fee_recipient.sdk_expect("ERR_FEE_RECIPIENT_NOT_SET"); + require!( + message.get_origin_chain() == native_fee_recipient.get_chain(), + "ERR_WRONG_FEE_RECIPIENT_CHAIN" + ); + if message.get_origin_chain() == ChainKind::Near { - let OmniAddress::Near(recipient) = native_fee_recipient else { + let OmniAddress::Near(recipient) = &native_fee_recipient else { env::panic_str("ERR_WRONG_CHAIN_KIND") }; Promise::new(recipient.parse().sdk_expect("ERR_PARSE_FEE_RECIPIENT")) @@ -622,7 +681,7 @@ impl Contract { &message.get_transfer_id(), &Some(NativeFee { amount: message.fee.native_fee, - recipient: native_fee_recipient, + recipient: native_fee_recipient.clone(), }), ); @@ -634,7 +693,15 @@ impl Contract { } } - ext_token::ext(message.token) + let token = message.token.clone(); + env::log_str( + &Nep141LockerEvent::ClaimFeeEvent { + transfer_message: message, + } + .to_log_string(), + ); + + ext_token::ext(token) .with_static_gas(LOG_METADATA_GAS) .ft_transfer(fin_transfer.fee_recipient, U128(fee), None) } @@ -650,27 +717,59 @@ impl Contract { }) .then( Self::ext(env::current_account_id()) - .with_attached_deposit(env::attached_deposit()) + .with_attached_deposit(NO_DEPOSIT) .with_static_gas(BIND_TOKEN_CALLBACK_GAS) - .bind_token_callback(), + .bind_token_callback(near_sdk::env::attached_deposit()), + ) + .then( + Self::ext(env::current_account_id()) + .with_attached_deposit(env::attached_deposit()) + .with_static_gas(BIND_TOKEN_REFUND_GAS) + .bind_token_refund(near_sdk::env::predecessor_account_id()), ) } #[private] pub fn bind_token_callback( &mut self, - #[callback_result] - #[serializer(borsh)] - call_result: Result, - ) { + attached_deposit: NearToken, + #[callback_result] call_result: Result, + ) -> NearToken { let Ok(ProverResult::DeployToken(deploy_token)) = call_result else { - env::panic_str("Invalid proof message") + env::panic_str("ERROR: Invalid proof message"); }; + require!( + self.factories + .get(&deploy_token.emitter_address.get_chain()) + == Some(deploy_token.emitter_address), + "Unknown factory" + ); + + let storage_usage = env::storage_usage(); self.tokens_to_address_mapping.insert( &(deploy_token.token_address.get_chain(), deploy_token.token), &deploy_token.token_address, ); + let required_deposit = env::storage_byte_cost() + .saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into()); + + require!( + attached_deposit >= required_deposit, + "ERROR: The deposit is not sufficient to cover the storage." + ); + attached_deposit.saturating_sub(required_deposit) + } + + #[private] + #[payable] + pub fn bind_token_refund( + &mut self, + predecessor_account_id: AccountId, + #[callback_result] call_result: Result, + ) { + let refund_amount = call_result.unwrap_or(env::attached_deposit()); + Self::refund(predecessor_account_id, refund_amount); } pub fn get_token_address( @@ -684,14 +783,15 @@ impl Contract { pub fn get_transfer_message(&self, nonce: U128) -> TransferMessage { self.pending_transfers .get(&nonce.0) - .map(|m| m.into_main().message) + .map(storage::TransferMessageStorage::into_main) + .map(|m| m.message) .sdk_expect("The transfer does not exist") } pub fn get_transfer_message_storage(&self, nonce: U128) -> TransferMessageStorageValue { self.pending_transfers .get(&nonce.0) - .map(|m| m.into_main()) + .map(storage::TransferMessageStorage::into_main) .sdk_expect("The transfer does not exist") } @@ -740,7 +840,7 @@ impl Contract { .flatten() .is_some() } - _ => false, + PromiseResult::Failed => false, } } @@ -775,7 +875,7 @@ impl Contract { let storage_usage = env::storage_usage(); require!( self.insert_raw_transfer(nonce, transfer_message, message_owner) - .is_some(), + .is_none(), "ERR_KEY_EXIST" ); env::storage_byte_cost().saturating_mul((env::storage_usage() - storage_usage).into()) @@ -786,7 +886,7 @@ impl Contract { let transfer = self .pending_transfers .remove(&nonce) - .map(|m| m.into_main()) + .map(storage::TransferMessageStorage::into_main) .sdk_expect("ERR_TRANSFER_NOT_EXIST"); let refund = @@ -835,10 +935,10 @@ impl Contract { attached_deposit: NearToken, ) { if attached_deposit >= required_balance { - let refund = attached_deposit.saturating_sub(required_balance); - if !refund.is_zero() { - Promise::new(account_id).transfer(refund); - } + Self::refund( + account_id, + attached_deposit.saturating_sub(required_balance), + ); } else { let required_balance = required_balance.saturating_sub(attached_deposit); let mut storage_balance = self @@ -855,4 +955,10 @@ impl Contract { } } } + + fn refund(account_id: AccountId, amount: NearToken) { + if !amount.is_zero() { + Promise::new(account_id).transfer(amount); + } + } } diff --git a/near/nep141-locker/src/storage.rs b/near/nep141-locker/src/storage.rs index 6415be5d..e64dbaa2 100644 --- a/near/nep141-locker/src/storage.rs +++ b/near/nep141-locker/src/storage.rs @@ -2,7 +2,10 @@ use near_contract_standards::storage_management::{StorageBalance, StorageBalance use near_sdk::{assert_one_yocto, borsh}; use near_sdk::{env, near_bindgen, AccountId, NearToken}; -use crate::*; +use crate::{ + require, BorshDeserialize, BorshSerialize, ChainKind, Contract, ContractExt, Deserialize, Fee, + OmniAddress, Promise, SdkExpect, Serialize, TransferMessage, U128, +}; #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] pub struct TransferMessageStorageValue { @@ -111,7 +114,7 @@ impl Contract { } pub fn storage_balance_of(&self, account_id: &AccountId) -> Option { - self.accounts_balances.get(&account_id) + self.accounts_balances.get(account_id) } pub fn required_balance_for_account(&self) -> NearToken { @@ -160,6 +163,21 @@ impl Contract { env::storage_byte_cost().saturating_mul((Self::get_basic_storage() + key_len).into()) } + pub fn required_balance_for_bind_token(&self, deploy_token_address: &OmniAddress) -> NearToken { + let max_token_id: AccountId = "a".repeat(64).parse().sdk_expect("ERR_PARSE_ACCOUNT_ID"); + + let key_len = borsh::to_vec(&(deploy_token_address.get_chain(), max_token_id)) + .sdk_expect("ERR_BORSH") + .len() as u64; + + let value_len = borsh::to_vec(deploy_token_address) + .sdk_expect("ERR_BORSH") + .len() as u64; + + env::storage_byte_cost() + .saturating_mul((Self::get_basic_storage() + key_len + value_len).into()) + } + fn get_basic_storage() -> u64 { const EXTRA_BYTES_RECORD: u64 = 40; const EXTRA_KEY_PREFIX_LEN: u64 = 1; diff --git a/near/omni-prover/evm-prover/src/lib.rs b/near/omni-prover/evm-prover/src/lib.rs index 9087a882..ef5ca274 100644 --- a/near/omni-prover/evm-prover/src/lib.rs +++ b/near/omni-prover/evm-prover/src/lib.rs @@ -1,5 +1,5 @@ use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::{env, ext_contract, near_bindgen, AccountId, Gas, PanicOnDefault, Promise}; +use near_sdk::{env, ext_contract, near_bindgen, require, AccountId, Gas, PanicOnDefault, Promise}; use omni_types::evm::events::parse_evm_event; use omni_types::evm::header::BlockHeader; use omni_types::evm::receipt::{LogEntry, Receipt}; @@ -42,6 +42,11 @@ impl EvmProver { } } + /// # Panics + /// + /// This function will panic in the following situations: + /// - If the log entry at the specified index doesn't match the decoded log entry. + #[allow(clippy::needless_pass_by_value)] #[handle_result] pub fn verify_proof(&self, #[serializer(borsh)] input: Vec) -> Result { let args = EvmVerifyProofArgs::try_from_slice(&input).map_err(|_| "ERR_PARSE_ARGS")?; @@ -54,7 +59,7 @@ impl EvmProver { // Verify log_entry included in receipt let log_index_usize = usize::try_from(evm_proof.log_index).map_err(|e| e.to_string())?; - assert_eq!(receipt.logs[log_index_usize], log_entry); + require!(receipt.logs[log_index_usize] == log_entry); // Verify receipt included into header let data = Self::verify_trie_proof( @@ -82,6 +87,7 @@ impl EvmProver { )) } + #[allow(clippy::needless_pass_by_value)] #[private] #[handle_result] pub fn verify_proof_callback( @@ -136,6 +142,7 @@ impl EvmProver { Self::_verify_trie_proof(expected_root.to_vec(), &actual_key, proof, 0, 0) } + #[allow(clippy::needless_pass_by_value)] fn _verify_trie_proof( expected_root: Vec, key: &Vec, @@ -147,24 +154,27 @@ impl EvmProver { if key_index == 0 { // trie root is always a hash - assert_eq!(keccak256(node), expected_root.as_slice()); + require!(keccak256(node) == expected_root.as_slice()); } else if node.len() < 32 { // if rlp < 32 bytes, then it is not hashed - assert_eq!(node.as_slice(), expected_root); + require!(node.as_slice() == expected_root); } else { - assert_eq!(keccak256(node), expected_root.as_slice()); + require!(keccak256(node) == expected_root.as_slice()); } - let node = Rlp::new(&node.as_slice()); + let node = Rlp::new(node.as_slice()); if node.iter().count() == 17 { // Branch node if key_index >= key.len() { - assert_eq!(proof_index + 1, proof.len()); + require!(proof_index + 1 == proof.len()); get_vec(&node, 16) } else { let new_expected_root = get_vec(&node, key[key_index] as usize); - if !new_expected_root.is_empty() { + if new_expected_root.is_empty() { + // not included in proof + vec![] + } else { Self::_verify_trie_proof( new_expected_root, key, @@ -172,19 +182,16 @@ impl EvmProver { key_index + 1, proof_index + 1, ) - } else { - // not included in proof - vec![] } } } else { // Leaf or extension node - assert_eq!(node.iter().count(), 2); + require!(node.iter().count() == 2); let path_u8 = get_vec(&node, 0); // Extract first nibble let head = path_u8[0] / 16; - // assert!(0 <= head); is implicit because of type limits - assert!(head <= 3); + // require!(0 <= head); is implicit because of type limits + require!(head <= 3); // Extract path let mut path = vec![]; @@ -198,8 +205,8 @@ impl EvmProver { if head >= 2 { // Leaf node - assert_eq!(proof_index + 1, proof.len()); - assert_eq!(key_index + path.len(), key.len()); + require!(proof_index + 1 == proof.len()); + require!(key_index + path.len() == key.len()); if path.as_slice() == &key[key_index..key_index + path.len()] { get_vec(&node, 1) } else { @@ -207,7 +214,7 @@ impl EvmProver { } } else { // Extension node - assert_eq!(path.as_slice(), &key[key_index..key_index + path.len()]); + require!(path.as_slice() == &key[key_index..key_index + path.len()]); let new_expected_root = get_vec(&node, 1); Self::_verify_trie_proof( new_expected_root, diff --git a/near/omni-prover/wormhole-omni-prover-proxy/src/lib.rs b/near/omni-prover/wormhole-omni-prover-proxy/src/lib.rs index 3e9647ae..57cab9d8 100644 --- a/near/omni-prover/wormhole-omni-prover-proxy/src/lib.rs +++ b/near/omni-prover/wormhole-omni-prover-proxy/src/lib.rs @@ -8,12 +8,12 @@ use omni_types::prover_result::{ProofKind, ProverResult}; mod byte_utils; mod parsed_vaa; -/// Gas to call verify_log_entry on prover. +/// Gas to call `verify_log_entry` on prover. pub const VERIFY_LOG_ENTRY_GAS: Gas = Gas::from_tgas(50); #[ext_contract(ext_prover)] pub trait Prover { - fn verify_vaa(&self, vaa: &String) -> u32; + fn verify_vaa(&self, vaa: &str) -> u32; } #[near_bindgen] @@ -31,11 +31,12 @@ impl WormholeOmniProverProxy { Self { prover_account } } + #[allow(clippy::needless_pass_by_value)] pub fn verify_proof(&self, #[serializer(borsh)] input: Vec) -> Promise { let args = WormholeVerifyProofArgs::try_from_slice(&input) .unwrap_or_else(|_| env::panic_str("ErrorOnArgsParsing")); - env::log_str(&format!("{}", args.vaa)); + env::log_str(&args.vaa); ext_prover::ext(self.prover_account.clone()) .with_static_gas(VERIFY_LOG_ENTRY_GAS) @@ -47,13 +48,19 @@ impl WormholeOmniProverProxy { ) } + /// # Panics + /// + /// This function will panic in the following situations: + /// - If the `vaa` string cannot be decoded as a valid hexadecimal string. + /// - If the `ParsedVAA::parse` function fails to parse the decoded VAA data. + /// - If the `proof_kind` doesn't match the first byte of the VAA payload. #[private] #[handle_result] pub fn verify_vaa_callback( &mut self, proof_kind: ProofKind, vaa: String, - #[callback_result] gov_idx: Result, + #[callback_result] gov_idx: &Result, ) -> Result { if gov_idx.is_err() { return Err("Proof is not valid!".to_owned()); diff --git a/near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs b/near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs index 75b8159e..a5d8e8ac 100644 --- a/near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs +++ b/near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs @@ -11,7 +11,7 @@ use { }; // Validator Action Approval(VAA) data - +#[allow(dead_code)] pub struct ParsedVAA { pub version: u8, pub guardian_set_index: u32, @@ -69,7 +69,7 @@ impl ParsedVAA { // Load 4 bytes starting from index 1 let guardian_set_index: u32 = data.get_u32(Self::GUARDIAN_SET_INDEX_POS); let len_signers = data.get_u8(Self::LEN_SIGNER_POS) as usize; - let body_offset: usize = Self::HEADER_LEN + Self::SIGNATURE_LEN * len_signers as usize; + let body_offset: usize = Self::HEADER_LEN + Self::SIGNATURE_LEN * len_signers; // Hash the body if body_offset >= data.len() { @@ -99,7 +99,7 @@ impl ParsedVAA { guardian_set_index, timestamp, nonce, - len_signers: len_signers as usize, + len_signers, emitter_chain, emitter_address, sequence, diff --git a/near/omni-tests/src/lib.rs b/near/omni-tests/src/lib.rs index f9fe2017..b59cfea5 100644 --- a/near/omni-tests/src/lib.rs +++ b/near/omni-tests/src/lib.rs @@ -5,7 +5,7 @@ mod tests { use omni_types::{ locker_args::{FinTransferArgs, StorageDepositArgs}, prover_result::{InitTransferMessage, ProverResult}, - OmniAddress, TransferMessage, + Fee, OmniAddress, TransferMessage, }; const MOCK_TOKEN_PATH: &str = "./../target/wasm32-unknown-unknown/release/mock_token.wasm"; @@ -209,6 +209,7 @@ mod tests { .call(locker_contract.id(), "fin_transfer") .args_borsh(FinTransferArgs { chain_kind: omni_types::ChainKind::Eth, + native_fee_recipient: OmniAddress::Near(account_1().to_string()), storage_deposit_args: StorageDepositArgs { token: token_contract.id().clone(), accounts: storage_deposit_accounts, @@ -220,7 +221,10 @@ mod tests { token: token_contract.id().clone(), recipient: OmniAddress::Near(account_1().to_string()), amount: U128(amount), - fee: U128(fee), + fee: Fee { + fee: U128(fee), + native_fee: U128(0), + }, sender: eth_eoa_address(), }, })) diff --git a/near/omni-types/src/evm/events.rs b/near/omni-types/src/evm/events.rs index dc39b7e9..54c93099 100644 --- a/near/omni-types/src/evm/events.rs +++ b/near/omni-types/src/evm/events.rs @@ -38,6 +38,7 @@ sol! { ); } +#[allow(clippy::needless_pass_by_value)] pub fn parse_evm_event>>( chain_kind: ChainKind, log_rlp: Vec, diff --git a/near/omni-types/src/evm/mod.rs b/near/omni-types/src/evm/mod.rs index 220e5692..f1845061 100644 --- a/near/omni-types/src/evm/mod.rs +++ b/near/omni-types/src/evm/mod.rs @@ -1,4 +1,4 @@ -pub mod header; pub mod events; +pub mod header; pub mod receipt; -pub mod utils; \ No newline at end of file +pub mod utils; diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 6382fb85..aaecb667 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -198,7 +198,7 @@ impl fmt::Display for OmniAddress { OmniAddress::Arb(recipient) => ("arb", recipient.to_string()), OmniAddress::Base(recipient) => ("base", recipient.to_string()), }; - write!(f, "{}:{}", chain_str, recipient) + write!(f, "{chain_str}:{recipient}") } } @@ -323,8 +323,16 @@ impl TransferMessage { } } +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] +pub enum PayloadType { + TransferMessage, + Metadata, + ClaimNativeFee, +} + #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] pub struct TransferMessagePayload { + pub prefix: PayloadType, pub nonce: U128, pub token: AccountId, pub amount: U128, @@ -334,11 +342,21 @@ pub struct TransferMessagePayload { #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] pub struct ClaimNativeFeePayload { + pub prefix: PayloadType, pub nonces: Vec, pub amount: U128, pub recipient: OmniAddress, } +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] +pub struct MetadataPayload { + pub prefix: PayloadType, + pub token: String, + pub name: String, + pub symbol: String, + pub decimals: u8, +} + #[derive(Deserialize, Serialize, Clone)] #[serde(crate = "near_sdk::serde")] pub struct SignRequest { @@ -353,14 +371,6 @@ pub enum UpdateFee { Proof(Vec), } -#[derive(Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)] -pub struct MetadataPayload { - pub token: String, - pub name: String, - pub symbol: String, - pub decimals: u8, -} - pub type Nonce = u128; pub fn stringify(item: T) -> String { @@ -384,4 +394,14 @@ mod test { assert_eq!(serialized, format!("\"eth:{address_str}\"")); assert_eq!(address, deserialized); } + + #[test] + fn test_payload_prefix() { + let res = borsh::to_vec(&PayloadType::TransferMessage).unwrap(); + assert_eq!(hex::encode(res), "00"); + let res = borsh::to_vec(&PayloadType::Metadata).unwrap(); + assert_eq!(hex::encode(res), "01"); + let res = borsh::to_vec(&PayloadType::ClaimNativeFee).unwrap(); + assert_eq!(hex::encode(res), "02"); + } } diff --git a/near/omni-types/src/locker_args.rs b/near/omni-types/src/locker_args.rs index 6c02a516..1278c26a 100644 --- a/near/omni-types/src/locker_args.rs +++ b/near/omni-types/src/locker_args.rs @@ -13,7 +13,7 @@ pub struct StorageDepositArgs { #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct FinTransferArgs { pub chain_kind: ChainKind, - pub native_fee_recipient: OmniAddress, + pub native_fee_recipient: Option, pub storage_deposit_args: StorageDepositArgs, pub prover_args: Vec, } @@ -22,7 +22,7 @@ pub struct FinTransferArgs { pub struct ClaimFeeArgs { pub chain_kind: ChainKind, pub prover_args: Vec, - pub native_fee_recipient: OmniAddress, + pub native_fee_recipient: Option, } #[derive(BorshDeserialize, BorshSerialize, Clone)] diff --git a/near/omni-types/src/mpc_types.rs b/near/omni-types/src/mpc_types.rs index ed5a59d8..fb3320d9 100644 --- a/near/omni-types/src/mpc_types.rs +++ b/near/omni-types/src/mpc_types.rs @@ -19,9 +19,19 @@ pub struct SignatureResponse { } impl SignatureResponse { + /// # Panics + /// + /// This function will panic in the following cases: + /// - If `self.big_r.affine_point` is not a valid hexadecimal string. + /// - If `self.s.scalar` is not a valid hexadecimal string. + /// - If the decoded `self.big_r.affine_point` is shorter than 1 byte. + /// + /// The error message "Incorrect Signature" will be displayed in case of a panic. pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); - bytes.extend_from_slice(&hex::decode(&self.big_r.affine_point).expect("Incorrect Signature")[1..]); + bytes.extend_from_slice( + &hex::decode(&self.big_r.affine_point).expect("Incorrect Signature")[1..], + ); bytes.extend_from_slice(&hex::decode(&self.s.scalar).expect("Incorrect Signature")); bytes.push(self.recovery_id + 27); diff --git a/near/omni-types/src/near_events.rs b/near/omni-types/src/near_events.rs index 643a5355..eb2c18b4 100644 --- a/near/omni-types/src/near_events.rs +++ b/near/omni-types/src/near_events.rs @@ -29,6 +29,9 @@ pub enum Nep141LockerEvent { signature: SignatureResponse, claim_payload: ClaimNativeFeePayload, }, + ClaimFeeEvent { + transfer_message: TransferMessage, + }, } impl Nep141LockerEvent {