Skip to content

Commit

Permalink
Add nonce verification for deposits
Browse files Browse the repository at this point in the history
  • Loading branch information
kiseln committed Sep 5, 2024
1 parent 8aaa89d commit ff7fdbe
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
13 changes: 13 additions & 0 deletions erc20-bridge-token/contracts/BridgeTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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),
Expand All @@ -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);
}

Expand Down
72 changes: 72 additions & 0 deletions erc20-bridge-token/test/BridgeToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit ff7fdbe

Please sign in to comment.