diff --git a/evm/src/omni-bridge/contracts/OmniBridge.sol b/evm/src/omni-bridge/contracts/OmniBridge.sol index e2354237..5c869767 100644 --- a/evm/src/omni-bridge/contracts/OmniBridge.sol +++ b/evm/src/omni-bridge/contracts/OmniBridge.sol @@ -42,6 +42,7 @@ contract OmniBridge is error InvalidSignature(); error NonceAlreadyUsed(uint64 nonce); error InvalidFee(); + error InvalidValue(); error FailedToSendEther(); function initialize( @@ -235,7 +236,6 @@ contract OmniBridge is } uint256 extensionValue; - if (tokenAddress == address(0)) { if (fee != 0) { revert InvalidFee(); @@ -259,16 +259,20 @@ contract OmniBridge is } function initTransferExtension( - address sender, - address tokenAddress, - uint64 originNonce, - uint128 amount, - uint128 fee, - uint128 nativeFee, - string calldata recipient, - string calldata message, + address /*sender*/, + address /*tokenAddress*/, + uint64 /*originNonce*/, + uint128 /*amount*/, + uint128 /*fee*/, + uint128 /*nativeFee*/, + string calldata /*recipient*/, + string calldata /*message*/, uint256 value - ) internal virtual {} + ) internal virtual { + if (value != 0) { + revert InvalidValue(); + } + } function pause(uint flags) external onlyRole(DEFAULT_ADMIN_ROLE) { _pause(flags); diff --git a/evm/src/omni-bridge/contracts/OmniBridgeWormhole.sol b/evm/src/omni-bridge/contracts/OmniBridgeWormhole.sol index 855e556b..27b3134e 100644 --- a/evm/src/omni-bridge/contracts/OmniBridgeWormhole.sol +++ b/evm/src/omni-bridge/contracts/OmniBridgeWormhole.sol @@ -11,6 +11,8 @@ interface IWormhole { bytes memory payload, uint8 consistencyLevel ) external payable returns (uint64 sequence); + + function messageFee() external view returns (uint256); } enum MessageType { diff --git a/evm/src/omni-bridge/contracts/test/TestWormhole.sol b/evm/src/omni-bridge/contracts/test/TestWormhole.sol index f164fefe..dc3a2e59 100644 --- a/evm/src/omni-bridge/contracts/test/TestWormhole.sol +++ b/evm/src/omni-bridge/contracts/test/TestWormhole.sol @@ -12,4 +12,8 @@ contract TestWormhole { emit MessagePublished(nonce, payload, consistencyLevel); return 0; } -} \ No newline at end of file + + function messageFee() external pure returns (uint256) { + return 10000; + } +} diff --git a/evm/tests/BridgeToken.ts b/evm/tests/BridgeToken.ts index ca144597..c3dacd96 100644 --- a/evm/tests/BridgeToken.ts +++ b/evm/tests/BridgeToken.ts @@ -101,7 +101,7 @@ describe("BridgeToken", () => { ).to.be.revertedWithCustomError(OmniBridge, "AccessControlUnauthorizedAccount") }) - it("deposit token", async () => { + it("can fin transfer", async () => { const { token } = await createToken(wrappedNearId) const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -123,7 +123,7 @@ describe("BridgeToken", () => { ) }) - it("can't deposit if the contract is paused", async () => { + it("can't fin transfer if the contract is paused", async () => { await createToken(wrappedNearId) const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -136,7 +136,7 @@ describe("BridgeToken", () => { await expect(OmniBridge.finTransfer(signature, payload)).to.be.revertedWith("Pausable: paused") }) - it("can't deposit twice with the same signature", async () => { + it("can't fin transfer twice with the same signature", async () => { await createToken(wrappedNearId) const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -149,7 +149,7 @@ describe("BridgeToken", () => { ) }) - it("can't deposit with invalid amount", async () => { + it("can't fin transfer with invalid amount", async () => { await createToken(wrappedNearId) const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -162,7 +162,7 @@ describe("BridgeToken", () => { ) }) - it("can't deposit with invalid nonce", async () => { + it("can't fin transfer with invalid nonce", async () => { await createToken(wrappedNearId) const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -175,7 +175,7 @@ describe("BridgeToken", () => { ) }) - it("can't deposit with invalid token", async () => { + it("can't fin transfer with invalid token", async () => { await createToken(wrappedNearId) const wrappedNearTokenAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -189,7 +189,7 @@ describe("BridgeToken", () => { ) }) - it("can't deposit with invalid recipient", async () => { + it("can't fin transfer with invalid recipient", async () => { await createToken(wrappedNearId) const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -202,7 +202,7 @@ describe("BridgeToken", () => { ) }) - it("can't deposit with invalid relayer", async () => { + it("can't fin transfer with invalid relayer", async () => { await createToken(wrappedNearId) const wrappedNearTokenAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -215,7 +215,7 @@ describe("BridgeToken", () => { ) }) - it("withdraw token", async () => { + it("can init transfer", async () => { const { token } = await createToken(wrappedNearId) const tokenProxyAddress = await token.getAddress() @@ -242,7 +242,7 @@ describe("BridgeToken", () => { expect((await token.balanceOf(user1.address)).toString()).to.be.equal("0") }) - it("cant withdraw token when paused", async () => { + it("can't init transfer token when paused", async () => { await createToken(wrappedNearId) const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) @@ -250,7 +250,7 @@ describe("BridgeToken", () => { await OmniBridge.finTransfer(signature, payload) const fee = 0 - const nativeFee = 0 + const nativeFee = 100 const message = "" await expect(OmniBridge.pause(PauseMode.PausedInitTransfer)) .to.emit(OmniBridge, "Paused") @@ -263,11 +263,63 @@ describe("BridgeToken", () => { nativeFee, "testrecipient.near", message, + { + value: 100, + }, ), ).to.be.revertedWith("Pausable: paused") }) - it("can deposit and withdraw after unpausing", async () => { + it("can't init transfer when value is too low", async () => { + await createToken(wrappedNearId) + const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) + + const { signature, payload } = depositSignature(tokenProxyAddress, user1.address) + await OmniBridge.finTransfer(signature, payload) + + const fee = 0 + const nativeFee = 100 + const message = "" + + await expect( + OmniBridge.initTransfer( + tokenProxyAddress, + payload.amount, + fee, + nativeFee, + "testrecipient.near", + message, + ), + ).to.be.revertedWithCustomError(OmniBridge, "InvalidValue") + }) + + it("can't init transfer when value is too high", async () => { + await createToken(wrappedNearId) + const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) + + const { signature, payload } = depositSignature(tokenProxyAddress, user1.address) + await OmniBridge.finTransfer(signature, payload) + + const fee = 0 + const nativeFee = 100 + const message = "" + + await expect( + OmniBridge.initTransfer( + tokenProxyAddress, + payload.amount, + fee, + nativeFee, + "testrecipient.near", + message, + { + value: 200, + }, + ), + ).to.be.revertedWithCustomError(OmniBridge, "InvalidValue") + }) + + it("can fin and init transfer after unpausing", async () => { const { token } = await createToken(wrappedNearId) const tokenProxyAddress = await token.getAddress() @@ -313,7 +365,7 @@ describe("BridgeToken", () => { expect((await BridgeTokenV2Proxied.decimals()).toString()).to.equal("24") }) - it("user cant upgrade token contract", async () => { + it("user can't upgrade token contract", async () => { await createToken(wrappedNearId) const tokenProxyAddress = await OmniBridge.nearToEthToken(wrappedNearId) diff --git a/evm/tests/BridgeTokenWormhole.ts b/evm/tests/BridgeTokenWormhole.ts index c091a004..2300f042 100644 --- a/evm/tests/BridgeTokenWormhole.ts +++ b/evm/tests/BridgeTokenWormhole.ts @@ -138,7 +138,7 @@ describe("BridgeTokenWormhole", () => { .withArgs(0, anyValue, consistencyLevel) }) - it("deposit token", async () => { + it("fin transfer", async () => { const { token } = await createToken(wrappedNearId) const tokenProxyAddress = await token.getAddress() const { signature, payload } = depositSignature(tokenProxyAddress, await user1.getAddress()) @@ -163,7 +163,7 @@ describe("BridgeTokenWormhole", () => { ) }) - it("withdraw token", async () => { + it("init transfer", async () => { const { token } = await createToken(wrappedNearId) const tokenProxyAddress = await token.getAddress() const { signature, payload } = depositSignature(tokenProxyAddress, await user1.getAddress()) @@ -197,6 +197,9 @@ describe("BridgeTokenWormhole", () => { nativeFee, recipient, message, + { + value: 10000, + }, ), ) .to.emit(TestWormhole, "MessagePublished") @@ -204,4 +207,22 @@ describe("BridgeTokenWormhole", () => { expect((await token.balanceOf(await user1.getAddress())).toString()).to.be.equal("0") }) + + it("can't init transfer without enough value", async () => { + const { token } = await createToken(wrappedNearId) + const tokenProxyAddress = await token.getAddress() + const { signature, payload } = depositSignature(tokenProxyAddress, await user1.getAddress()) + await OmniBridgeWormhole.finTransfer(signature, payload) + + await expect( + OmniBridgeWormhole.connect(user1).initTransfer( + tokenProxyAddress, + payload.amount, + 0, + 0, + "testrecipient.near", + "", + ), + ).to.be.revertedWithCustomError(OmniBridgeWormhole, "InvalidValue") + }) }) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index f2d6ec39..ed759cbf 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -952,30 +952,35 @@ impl Contract { Promise::new(recipient) .transfer(NearToken::from_yoctonear(amount_to_transfer.0)), ) - } else { - let transfer_promise = ext_token::ext(token.clone()).with_attached_deposit(ONE_YOCTO); - if is_deployed_token { - transfer_promise - .with_static_gas(MINT_TOKEN_GAS.saturating_add(FT_TRANSFER_CALL_GAS)) - .mint( - recipient, - amount_to_transfer, - (!transfer_message.msg.is_empty()).then(|| transfer_message.msg.clone()), - ) - } else if transfer_message.msg.is_empty() { - transfer_promise - .with_static_gas(FT_TRANSFER_GAS) - .ft_transfer(recipient, amount_to_transfer, None) + } else if is_deployed_token { + let deposit = if transfer_message.msg.is_empty() { + NO_DEPOSIT } else { - transfer_promise - .with_static_gas(FT_TRANSFER_CALL_GAS) - .ft_transfer_call( - recipient, - amount_to_transfer, - None, - transfer_message.msg.clone(), - ) - } + ONE_YOCTO + }; + ext_token::ext(token.clone()) + .with_attached_deposit(deposit) + .with_static_gas(MINT_TOKEN_GAS.saturating_add(FT_TRANSFER_CALL_GAS)) + .mint( + recipient, + amount_to_transfer, + (!transfer_message.msg.is_empty()).then(|| transfer_message.msg.clone()), + ) + } else if transfer_message.msg.is_empty() { + ext_token::ext(token.clone()) + .with_attached_deposit(ONE_YOCTO) + .with_static_gas(FT_TRANSFER_GAS) + .ft_transfer(recipient, amount_to_transfer, None) + } else { + ext_token::ext(token.clone()) + .with_attached_deposit(ONE_YOCTO) + .with_static_gas(FT_TRANSFER_CALL_GAS) + .ft_transfer_call( + recipient, + amount_to_transfer, + None, + transfer_message.msg.clone(), + ) }; if transfer_message.fee.fee.0 > 0 { @@ -988,15 +993,15 @@ impl Contract { ); storage_deposit_action_index += 1; - let transfer_fee_promise = ext_token::ext(token).with_attached_deposit(ONE_YOCTO); promise = promise.then(if is_deployed_token { - transfer_fee_promise.with_static_gas(MINT_TOKEN_GAS).mint( + ext_token::ext(token).with_static_gas(MINT_TOKEN_GAS).mint( predecessor_account_id.clone(), transfer_message.fee.fee, None, ) } else { - transfer_fee_promise + ext_token::ext(token) + .with_attached_deposit(ONE_YOCTO) .with_static_gas(FT_TRANSFER_GAS) .ft_transfer( predecessor_account_id.clone(), diff --git a/solana/bridge_token_factory/Cargo.lock b/solana/bridge_token_factory/Cargo.lock index d63cecb9..34478b18 100644 --- a/solana/bridge_token_factory/Cargo.lock +++ b/solana/bridge_token_factory/Cargo.lock @@ -164,7 +164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0bb0e0911ad4a70cab880cdd6287fe1e880a1a9d8e4e6defa8e9044b9796a6c" dependencies = [ "anchor-syn", - "borsh-derive-internal 0.9.3", + "borsh-derive-internal 0.10.4", "proc-macro2", "quote", "syn 1.0.109", @@ -200,7 +200,7 @@ dependencies = [ "arrayref", "base64 0.21.7", "bincode", - "borsh 0.9.3", + "borsh 0.10.4", "bytemuck", "getrandom 0.2.15", "solana-program", @@ -431,6 +431,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -637,6 +643,7 @@ dependencies = [ "anchor-spl", "bitvec", "cfg-if", + "libsecp256k1 0.7.1", "wormhole-anchor-sdk", ] @@ -1218,15 +1225,34 @@ dependencies = [ "base64 0.12.3", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", + "libsecp256k1-core 0.2.2", + "libsecp256k1-gen-ecmult 0.2.1", + "libsecp256k1-gen-genmult 0.2.1", "rand 0.7.3", "serde", "sha2 0.9.9", "typenum", ] +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core 0.3.0", + "libsecp256k1-gen-ecmult 0.3.0", + "libsecp256k1-gen-genmult 0.3.0", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + [[package]] name = "libsecp256k1-core" version = "0.2.2" @@ -1238,13 +1264,33 @@ dependencies = [ "subtle", ] +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + [[package]] name = "libsecp256k1-gen-ecmult" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" dependencies = [ - "libsecp256k1-core", + "libsecp256k1-core 0.2.2", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core 0.3.0", ] [[package]] @@ -1253,7 +1299,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" dependencies = [ - "libsecp256k1-core", + "libsecp256k1-core 0.2.2", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core 0.3.0", ] [[package]] @@ -1990,7 +2045,7 @@ dependencies = [ "js-sys", "lazy_static", "libc", - "libsecp256k1", + "libsecp256k1 0.6.0", "light-poseidon", "log", "memoffset", @@ -2040,7 +2095,7 @@ dependencies = [ "itertools", "js-sys", "lazy_static", - "libsecp256k1", + "libsecp256k1 0.6.0", "log", "memmap2", "num-derive 0.4.2", diff --git a/solana/bridge_token_factory/programs/bridge_token_factory/Cargo.toml b/solana/bridge_token_factory/programs/bridge_token_factory/Cargo.toml index 138659d0..abb59969 100644 --- a/solana/bridge_token_factory/programs/bridge_token_factory/Cargo.toml +++ b/solana/bridge_token_factory/programs/bridge_token_factory/Cargo.toml @@ -28,6 +28,7 @@ anchor-lang = { version = "0.30.1", features = ["init-if-needed"] } anchor-spl = { version = "0.30.1", features = ["metadata"] } cfg-if = "1.0.0" wormhole-anchor-sdk = { git = "https://github.com/aankor/wormhole-scaffolding.git", branch = "anchor0.30.1", default-features = false } +libsecp256k1 = "0.7.1" [dependencies.bitvec] diff --git a/solana/bridge_token_factory/programs/bridge_token_factory/src/error.rs b/solana/bridge_token_factory/programs/bridge_token_factory/src/error.rs index c2b5b840..e833aaee 100644 --- a/solana/bridge_token_factory/programs/bridge_token_factory/src/error.rs +++ b/solana/bridge_token_factory/programs/bridge_token_factory/src/error.rs @@ -6,6 +6,8 @@ pub enum ErrorCode { InvalidArgs, #[msg("Signature verification failed")] SignatureVerificationFailed, + #[msg("Malleable signature")] + MalleableSignature, #[msg("Nonce already used")] NonceAlreadyUsed, #[msg("Token metadata not provided")] diff --git a/solana/bridge_token_factory/programs/bridge_token_factory/src/instructions/user/init_transfer_sol.rs b/solana/bridge_token_factory/programs/bridge_token_factory/src/instructions/user/init_transfer_sol.rs index 45d67b2f..293373de 100644 --- a/solana/bridge_token_factory/programs/bridge_token_factory/src/instructions/user/init_transfer_sol.rs +++ b/solana/bridge_token_factory/programs/bridge_token_factory/src/instructions/user/init_transfer_sol.rs @@ -46,7 +46,8 @@ impl<'info> InitTransferSol<'info> { to: self.sol_vault.to_account_info(), }, ), - payload.native_fee + (payload.amount as u64), + payload.native_fee.checked_add(payload.amount.try_into().unwrap()) + .unwrap(), )?; self.wormhole.post_message(payload.serialize_for_near(( diff --git a/solana/bridge_token_factory/programs/bridge_token_factory/src/state/message/mod.rs b/solana/bridge_token_factory/programs/bridge_token_factory/src/state/message/mod.rs index 830b72a3..d0fcdbce 100644 --- a/solana/bridge_token_factory/programs/bridge_token_factory/src/state/message/mod.rs +++ b/solana/bridge_token_factory/programs/bridge_token_factory/src/state/message/mod.rs @@ -29,8 +29,17 @@ impl SignedPayload

{ let serialized = self.payload.serialize_for_near(params)?; let hash = keccak::hash(&serialized); + let signature_bytes = &self.signature[0..64]; + + let signature = libsecp256k1::Signature::parse_standard_slice(signature_bytes) + .map_err(|_| ProgramError::InvalidArgument)?; + require!( + !signature.s.is_high(), + ErrorCode::MalleableSignature + ); + let signer = - secp256k1_recover(&hash.to_bytes(), self.signature[64], &self.signature[0..64]) + secp256k1_recover(&hash.to_bytes(), self.signature[64], signature_bytes) .map_err(|_| error!(ErrorCode::SignatureVerificationFailed))?; require!(