From e86a9ae7f009508c7d4ba16224a998b13446d3de Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Wed, 4 Sep 2024 20:55:40 +0400 Subject: [PATCH] Add nonce verification for deposits --- .../contracts/BridgeTokenFactory.sol | 9 +++ erc20-bridge-token/test/BridgeToken.js | 72 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/erc20-bridge-token/contracts/BridgeTokenFactory.sol b/erc20-bridge-token/contracts/BridgeTokenFactory.sol index 3a9fd7c..c3bf486 100644 --- a/erc20-bridge-token/contracts/BridgeTokenFactory.sol +++ b/erc20-bridge-token/contracts/BridgeTokenFactory.sol @@ -30,6 +30,8 @@ contract BridgeTokenFactory is mapping(bytes => bool) private _whitelistedAccounts; bool private _isWhitelistModeEnabled; + mapping(uint128 => bool) private _completedTransfers; + address public tokenImplementationAddress; address public nearBridgeDerivedAddress; @@ -73,6 +75,7 @@ contract BridgeTokenFactory is ); error InvalidSignature(); + error NonceAlreadyUsed(); // BridgeTokenFactory is linked to the bridge token factory on NEAR side. // It also links to the prover that it uses to unlock the tokens. @@ -167,6 +170,10 @@ contract BridgeTokenFactory is } function deposit(bytes calldata signatureData, BridgeDeposit calldata bridgeDeposit) external whenNotPaused(PAUSED_DEPOSIT) { + if (_completedTransfers[bridgeDeposit.nonce]) { + revert NonceAlreadyUsed(); + } + bytes memory borshEncoded = bytes.concat( Borsh.encodeUint128(bridgeDeposit.nonce), Borsh.encodeString(bridgeDeposit.token), @@ -186,6 +193,8 @@ contract BridgeTokenFactory is require(_isBridgeToken[_nearToEthToken[bridgeDeposit.token]], "ERR_NOT_BRIDGE_TOKEN"); BridgeToken(_nearToEthToken[bridgeDeposit.token]).mint(bridgeDeposit.recipient, bridgeDeposit.amount); + _completedTransfers[bridgeDeposit.nonce] = true; + emit Deposit(bridgeDeposit.token, bridgeDeposit.amount, bridgeDeposit.recipient); } diff --git a/erc20-bridge-token/test/BridgeToken.js b/erc20-bridge-token/test/BridgeToken.js index d2a1381..ac46a0d 100644 --- a/erc20-bridge-token/test/BridgeToken.js +++ b/erc20-bridge-token/test/BridgeToken.js @@ -143,6 +143,78 @@ describe('BridgeToken', () => { .revertedWith('Pausable: paused'); }) + it('can\'t deposit twice with the same signature', async function () { + await createToken(wrappedNearId); + + const { signature, payload } = depositSignature(wrappedNearId, user1.address); + await BridgeTokenFactory.deposit(signature, payload); + + await expect( + BridgeTokenFactory.deposit(signature, payload) + ) + .to.be.revertedWithCustomError(BridgeTokenFactory, 'NonceAlreadyUsed'); + }) + + it('can\'t deposit with invalid amount', async function () { + await createToken(wrappedNearId); + + const { signature, payload } = depositSignature(wrappedNearId, user1.address); + payload.amount = 100000; + + await expect( + BridgeTokenFactory.deposit(signature, payload) + ) + .to.be.revertedWithCustomError(BridgeTokenFactory, 'InvalidSignature'); + }) + + it('can\'t deposit with invalid nonce', async function () { + await createToken(wrappedNearId); + + const { signature, payload } = depositSignature(wrappedNearId, user1.address); + payload.nonce = 99; + + await expect( + BridgeTokenFactory.deposit(signature, payload) + ) + .to.be.revertedWithCustomError(BridgeTokenFactory, 'InvalidSignature'); + }) + + it('can\'t deposit with invalid token', async function () { + await createToken(wrappedNearId); + + const { signature, payload } = depositSignature(wrappedNearId, user1.address); + payload.token = 'test-token.testnet'; + + await expect( + BridgeTokenFactory.deposit(signature, payload) + ) + .to.be.revertedWithCustomError(BridgeTokenFactory, 'InvalidSignature'); + }) + + it('can\'t deposit with invalid recipient', async function () { + await createToken(wrappedNearId); + + const { signature, payload } = depositSignature(wrappedNearId, user1.address); + payload.recipient = user2.address; + + await expect( + BridgeTokenFactory.deposit(signature, payload) + ) + .to.be.revertedWithCustomError(BridgeTokenFactory, 'InvalidSignature'); + }) + + it('can\'t deposit with invalid relayer', async function () { + await createToken(wrappedNearId); + + const { signature, payload } = depositSignature(wrappedNearId, user1.address); + payload.relayer = user2.address; + + await expect( + BridgeTokenFactory.deposit(signature, payload) + ) + .to.be.revertedWithCustomError(BridgeTokenFactory, 'InvalidSignature'); + }) + it('withdraw token', async function () { const { token } = await createToken(wrappedNearId);