From ff7fdbeb3b87dce0529bcddd3472d5dc9ad18750 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 | 13 ++++ erc20-bridge-token/test/BridgeToken.js | 72 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/erc20-bridge-token/contracts/BridgeTokenFactory.sol b/erc20-bridge-token/contracts/BridgeTokenFactory.sol index 3a9fd7c..94bc1fa 100644 --- a/erc20-bridge-token/contracts/BridgeTokenFactory.sol +++ b/erc20-bridge-token/contracts/BridgeTokenFactory.sol @@ -22,6 +22,10 @@ contract BridgeTokenFactory is 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; @@ -33,6 +37,8 @@ contract BridgeTokenFactory is address public tokenImplementationAddress; address public nearBridgeDerivedAddress; + mapping(uint128 => bool) private _completedTransfers; + bytes32 public constant PAUSABLE_ADMIN_ROLE = keccak256("PAUSABLE_ADMIN_ROLE"); uint constant UNPAUSED_ALL = 0; uint constant PAUSED_WITHDRAW = 1 << 0; @@ -73,6 +79,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 +174,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 +197,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);