From 0c5bb420c498a51aff9e1bb8a48859e59bd5961d Mon Sep 17 00:00:00 2001 From: tamirms Date: Thu, 8 Sep 2022 11:40:19 +0200 Subject: [PATCH 1/7] Address issues in smart contract from security audit --- solidity/contracts/Auth.sol | 31 ++++-- solidity/contracts/Bridge.sol | 58 +++++++++- solidity/test/bridge.js | 4 + solidity/test/erc20.js | 106 ++++++++++++------ solidity/test/eth.js | 102 +++++++++++------ solidity/test/paused.js | 52 +++++---- solidity/test/registerStellarAsset.js | 72 ++++++++---- solidity/test/setDepositAllowed.js | 153 ++++++++++++++++++++++++++ solidity/test/updateSigners.js | 53 ++++++--- 9 files changed, 487 insertions(+), 144 deletions(-) create mode 100644 solidity/test/setDepositAllowed.js diff --git a/solidity/contracts/Auth.sol b/solidity/contracts/Auth.sol index 93717e8..8c0e642 100644 --- a/solidity/contracts/Auth.sol +++ b/solidity/contracts/Auth.sol @@ -14,24 +14,29 @@ contract Auth { // minThreshold - the minimum amount of signers who need to approve a bridge transaction // for it to be valid. // version - a sequence number associated with the validator set. Whenver the validator - // set configuration is updated the version will increment. The current version is part of the - // payload for each bridge transaction. So, whenever the version is bumped that will invalidate - // any previously signed bridge transactions. + // set configuration is updated the version will increment. address[] public signers; uint8 public minThreshold; uint256 public version; + // domainSeparator is a value which is unique to the current bridge contract, chain id, and version. + // It is part of the payload for each bridge transaction. The purpose of the domain separator + // is to prevent replay attacks in case there are multiple bridge contracts deployed on the same + // chain or different chains using the same validator set. + // Also, since the version is included in the domain separator, whenever the validator set + // is updated that will invalidate any previously signed bridge transactions. + bytes32 public domainSeparator; // RegisterSigners is emitted whenever the validator set configuration is modified. - event RegisterSigners(uint256 version, address[] signers, uint8 minThreshold); + event RegisterSigners(uint256 version, bytes32 domainSeparator, address[] signers, uint8 minThreshold); // fulfilledrequests is a set of all bridge requests which have been completed. This // set is used to prevent an attacker from replaying bridge transactions. mapping(bytes32 => bool) private fulfilledrequests; constructor(address[] memory _signers, uint8 _minThreshold) { - _updateSigners(0, _signers, _minThreshold); + _updateSigners(0, _updateDomainSeparator(0), _signers, _minThreshold); } - function _updateSigners(uint256 newVersion, address[] memory _signers, uint8 _minThreshold) internal { + function _updateSigners(uint256 newVersion, bytes32 newDomainSeparator, address[] memory _signers, uint8 _minThreshold) internal { require(_signers.length > 0, "too few signers"); require(_signers.length < 256, "too many signers"); require(_minThreshold > _signers.length / 2, "min threshold is too low"); @@ -44,7 +49,13 @@ contract Auth { signers = _signers; minThreshold = _minThreshold; - emit RegisterSigners(newVersion, _signers, _minThreshold); + emit RegisterSigners(newVersion, newDomainSeparator, _signers, _minThreshold); + } + + function _updateDomainSeparator(uint256 _version) internal returns (bytes32) { + bytes32 h = keccak256(abi.encode(_version, block.chainid, this)); + domainSeparator = h; + return h; } // updateSigners() is called to update the validator set configuration for the bridge. @@ -59,10 +70,10 @@ contract Auth { bytes[] calldata signatures, uint8[] calldata indexes ) external { - uint256 newVersion = ++version; - bytes32 h = keccak256(abi.encode(newVersion-1, UPDATE_SIGNERS_ID, _signers, _minThreshold)); + bytes32 h = keccak256(abi.encode(domainSeparator, UPDATE_SIGNERS_ID, _signers, _minThreshold)); verifySignatures(h, signatures, indexes); - _updateSigners(newVersion, _signers, _minThreshold); + uint256 newVersion = ++version; + _updateSigners(newVersion, _updateDomainSeparator(newVersion), _signers, _minThreshold); } // verifySignatures() ensure that provided list of signatures map to the validator set diff --git a/solidity/contracts/Bridge.sol b/solidity/contracts/Bridge.sol index bd6519c..f1f25cd 100644 --- a/solidity/contracts/Bridge.sol +++ b/solidity/contracts/Bridge.sol @@ -45,8 +45,16 @@ struct SetPausedRequest { uint256 expiration; // unix timestamp of when the transaction should expire } +// SetDepositAllowedRequest is the payload for the setDepositAllowed() transaction. +struct SetDepositAllowedRequest { + address token; // token to be enabled / disabled + bool allowed; // true if depsits are allowed otherwise false + uint256 nonce; // used to make each transaction unique for replay prevention + uint256 expiration; // unix timestamp of when the transaction should expire +} + // RegisterStellarAssetRequest is the payload for the registerStellarAsset() transaction. -// The three fields define a new ERC20 token which represents the ethereum version of +// The three fields define a new ERC20 token which represents the ethereum equivalent of // a Stellar asset. struct RegisterStellarAssetRequest { uint8 decimals; @@ -61,6 +69,8 @@ uint8 constant PAUSE_WITHDRAWALS = 1 << 1; // bitwise flag representing the state where no withdrawals or deposits are allowed on the bridge uint8 constant PAUSE_DEPOSITS_AND_WITHDRAWALS = PAUSE_DEPOSITS | PAUSE_WITHDRAWALS; +// SET_DEPOSIT_ALLOWED is used to distinguish setDepositAllowed() signatures from signatures for other bridge functions. +bytes32 constant SET_DEPOSIT_ALLOWED = keccak256("setDepositAllowed"); // SET_PAUSED_ID is used to distinguish setPaused() signatures from signatures for other bridge functions. bytes32 constant SET_PAUSED_ID = keccak256("setPaused"); // REGISTER_STELLAR_ASSET_ID is used to distinguish registerStellarAsset() signatures from signatures for other bridge functions. @@ -77,7 +87,10 @@ contract Bridge is Auth { event SetPaused(uint8 value); // to create a Bridge instance you need to provide the validator set configuration - constructor(address[] memory _signers, uint8 _minThreshold) Auth(_signers, _minThreshold) {} + constructor(address[] memory _signers, uint8 _minThreshold) Auth(_signers, _minThreshold) { + emit SetDepositAllowed(address(0x0), true); + depositAllowed[address(0x0)] = true; + } // Deposit is emitted whenever ERC20 tokens (or ETH) are deposited on the bridge. // The Deposit event initiates a Ethereum -> Stellar transfer. @@ -108,6 +121,13 @@ contract Bridge is Auth { // created by the bridge. mapping(address => bool) public isStellarAsset; + // depositAllowed identifies whether an ERC20 token is can be deposited on + // the bridge. + mapping(address => bool) public depositAllowed; + + // SetPaused is emitted whenever the paused state of the bridge changes + event SetDepositAllowed(address token, bool allowed); + // depositERC20() deposits ERC20 tokens to the bridge and starts a ERC20 -> Stellar // transfer. If deposits are disabled this function will fail. function depositERC20( @@ -117,6 +137,9 @@ contract Bridge is Auth { ) external { require((paused & PAUSE_DEPOSITS) == 0, "deposits are paused"); require(amount > 0, "deposit amount is zero"); + require(token != address(0x0), "invalid token address"); + require(depositAllowed[token], "deposits not allowed for token"); + emit Deposit(token, msg.sender, destination, amount); if (isStellarAsset[token]) { @@ -134,6 +157,7 @@ contract Bridge is Auth { // depositETH() deposits ETH to the bridge and starts a ETH -> Stellar // transfer. If deposits are disabled this function will fail. function depositETH(uint256 destination) external payable { + require(depositAllowed[address(0)], "eth deposits are not allowed"); require((paused & PAUSE_DEPOSITS) == 0, "deposits are paused"); require(msg.value > 0, "deposit amount is zero"); emit Deposit(address(0), msg.sender, destination, msg.value); @@ -152,7 +176,7 @@ contract Bridge is Auth { ) external { require((paused & PAUSE_WITHDRAWALS) == 0, "withdrawals are paused"); verifyRequest( - keccak256(abi.encode(version, WITHDRAW_ERC20_ID, request)), + keccak256(abi.encode(domainSeparator, WITHDRAW_ERC20_ID, request)), request.id, request.expiration, signatures, @@ -189,7 +213,7 @@ contract Bridge is Auth { ) external { require((paused & PAUSE_WITHDRAWALS) == 0, "withdrawals are paused"); verifyRequest( - keccak256(abi.encode(version, WITHDRAW_ETH_ID, request)), + keccak256(abi.encode(domainSeparator, WITHDRAW_ETH_ID, request)), request.id, request.expiration, signatures, @@ -210,13 +234,33 @@ contract Bridge is Auth { uint8[] calldata indexes ) external { require(request.value <= PAUSE_DEPOSITS_AND_WITHDRAWALS, "invalid paused value"); - bytes32 requestHash = keccak256(abi.encode(version, SET_PAUSED_ID, request)); + bytes32 requestHash = keccak256(abi.encode(domainSeparator, SET_PAUSED_ID, request)); // ensure the same setPaused() transaction cannot be used more than once verifyRequest(requestHash, requestHash, request.expiration, signatures, indexes); emit SetPaused(request.value); paused = request.value; } + // setDepositAllowed() will enable or disable deposits for a specific token. + // setDepositAllowed() must be authorized by the bridge validators otherwise the transaction + // will fail. Replay prevention is implemented by storing the request hash in the + // fulfilledrequests set. + function setDepositAllowed( + SetDepositAllowedRequest memory request, + bytes[] calldata signatures, + uint8[] calldata indexes + ) external { + bytes32 requestHash = keccak256(abi.encode(domainSeparator, SET_DEPOSIT_ALLOWED, request)); + // ensure the same setPaused() transaction cannot be used more than once + verifyRequest(requestHash, requestHash, request.expiration, signatures, indexes); + emit SetDepositAllowed(request.token, request.allowed); + if (request.allowed) { + depositAllowed[request.token] = true; + } else { + delete(depositAllowed[request.token]); + } + } + // registerStellarAsset() will creates an ERC20 token to represent a stellar asset. // registerStellarAsset() must be authorized by the bridge validators otherwise the transaction // will fail. Replay prevention is impemented by creating the ERC20 via the CREATE2 opcode (see @@ -227,7 +271,7 @@ contract Bridge is Auth { uint8[] calldata indexes ) external { bytes32 requestHash = keccak256(abi.encode( - version, + domainSeparator, REGISTER_STELLAR_ASSET_ID, request.decimals, keccak256(bytes(request.name)), @@ -253,6 +297,8 @@ contract Bridge is Auth { ); emit RegisterStellarAsset(asset); + emit SetDepositAllowed(asset, true); isStellarAsset[asset] = true; + depositAllowed[asset] = true; } } diff --git a/solidity/test/bridge.js b/solidity/test/bridge.js index 79d6a8c..5291056 100644 --- a/solidity/test/bridge.js +++ b/solidity/test/bridge.js @@ -13,6 +13,7 @@ describe("Deploy Bridge", function() { for(let i = 0; i < 20; i++) { expect(await bridge.signers(i)).to.equal(addresses[i]); } + expect(await bridge.depositAllowed("0x0000000000000000000000000000000000000000")).to.be.true; await expect(bridge.signers(20)).to.be.reverted; }); @@ -26,10 +27,13 @@ describe("Deploy Bridge", function() { await expect(Bridge.deploy(addresses, i)).to.be.revertedWith("min threshold is too low"); } + let domainSeparator = ''; for (let i = 11; i <= 20; i++) { const bridge = await Bridge.deploy(addresses, i); expect(await bridge.minThreshold()).to.equal(i); expect(await bridge.version()).to.equal(0); + expect(await bridge.domainSeparator()).to.not.equal(domainSeparator); + domainSeparator = await bridge.domainSeparator() } await expect(Bridge.deploy(addresses, 21)).to.be.revertedWith("min threshold is too high"); diff --git a/solidity/test/erc20.js b/solidity/test/erc20.js index c1f089c..83ed1f4 100644 --- a/solidity/test/erc20.js +++ b/solidity/test/erc20.js @@ -3,12 +3,13 @@ const { ethers } = require("hardhat"); const { PAUSE_DEPOSITS, PAUSE_NOTHING, PAUSE_WITHDRAWALS_AND_DEPOSITS, setPaused, nextPauseNonce, PAUSE_WITHDRAWALS } = require("./paused"); const { updateSigners } = require("./updateSigners"); const { validTimestamp, expiredTimestamp } = require("./util"); +const { setDepositAllowed } = require("./setDepositAllowed"); -async function withdrawERC20(bridge, token, signers, id, configVersion, expiration, recipient, amount) { +async function withdrawERC20(bridge, token, signers, id, domainSeparator, expiration, recipient, amount) { const request = [id, expiration, recipient, token.address, amount]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], - [configVersion, ethers.utils.id("withdrawERC20"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawERC20"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); return bridge.withdrawERC20(request, signatures, [...Array(20).keys()]); @@ -18,20 +19,25 @@ describe("Deposit & Withdraw ERC20", function() { let signers; let bridge; let token; + let domainSeparator; + let ERC20; + let sender; this.beforeAll(async function() { signers = (await ethers.getSigners()).slice(0, 20); - const recipient = signers[0]; + sender = signers[0]; signers.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase())); const addresses = signers.map(a => a.address); const Bridge = await ethers.getContractFactory("Bridge"); bridge = await Bridge.deploy(addresses, 20); + domainSeparator = await bridge.domainSeparator(); - const ERC20 = await ethers.getContractFactory("StellarAsset"); + ERC20 = await ethers.getContractFactory("StellarAsset"); token = await ERC20.deploy("Test Token", "TEST", 18); - await token.mint(recipient.address, ethers.utils.parseEther("100.0")); + await token.mint(sender.address, ethers.utils.parseEther("100.0")); await token.approve(bridge.address, ethers.utils.parseEther("300.0")); + await setDepositAllowed(bridge, signers, domainSeparator, token.address, true, 0, validTimestamp()); }); it("deposits of 0 are rejected", async function() { @@ -39,13 +45,43 @@ describe("Deposit & Withdraw ERC20", function() { }); it("deposits are rejected when bridge is paused", async function() { - await setPaused(bridge, signers, 0, PAUSE_DEPOSITS, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_DEPOSITS, nextPauseNonce(), validTimestamp()); await expect(bridge.depositERC20( token.address, 1, ethers.utils.parseEther("1.0") )).to.be.revertedWith("deposits are paused"); - await setPaused(bridge, signers, 0, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + }); + + it("block deposits for a specific ERC20 token", async function() { + const blockedToken = await ERC20.deploy("Blocked Test Token", "BLOCKED", 18); + await blockedToken.mint(sender.address, ethers.utils.parseEther("100.0")); + await blockedToken.approve(bridge.address, ethers.utils.parseEther("300.0")); + + expect(await bridge.depositAllowed(blockedToken.address)).to.be.false; + await expect(bridge.depositERC20( + blockedToken.address, 1, ethers.utils.parseEther("1.0") + )).to.be.revertedWith("deposits not allowed for token"); + + await setDepositAllowed(bridge, signers, domainSeparator, blockedToken.address, true, 1, validTimestamp()); + expect(await bridge.depositAllowed(blockedToken.address)).to.be.true; + + const before = await token.balanceOf(bridge.address); + + await bridge.depositERC20( + blockedToken.address, 1, ethers.utils.parseEther("1.0") + ); + + const after = await blockedToken.balanceOf(bridge.address); + expect(after.sub(before)).to.equal(ethers.utils.parseEther("1.0")); + + await setDepositAllowed(bridge, signers, domainSeparator, blockedToken.address, false, 2, validTimestamp()); + + expect(await bridge.depositAllowed(blockedToken.address)).to.be.false; + await expect(bridge.depositERC20( + blockedToken.address, 1, ethers.utils.parseEther("1.0") + )).to.be.revertedWith("deposits not allowed for token"); }); it("cannot deposit more tokens than current balance", async function() { @@ -64,7 +100,7 @@ describe("Deposit & Withdraw ERC20", function() { }); it("deposits succeed when withdrawals are paused", async function() { - await setPaused(bridge, signers, 0, PAUSE_WITHDRAWALS, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_WITHDRAWALS, nextPauseNonce(), validTimestamp()); const before = await token.balanceOf(bridge.address); await bridge.depositERC20( @@ -73,11 +109,11 @@ describe("Deposit & Withdraw ERC20", function() { const after = await token.balanceOf(bridge.address); expect(after.sub(before)).to.equal(ethers.utils.parseEther("1.0")); - await setPaused(bridge, signers, 0, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); }); it("withdrawals are rejected when bridge is paused", async function() { - await setPaused(bridge, signers, 0, PAUSE_WITHDRAWALS, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_WITHDRAWALS, nextPauseNonce(), validTimestamp()); const recipient = signers[1].address; await expect(withdrawERC20( @@ -85,17 +121,17 @@ describe("Deposit & Withdraw ERC20", function() { token, signers, ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, validTimestamp(), recipient, ethers.utils.parseEther("1.0") )).to.be.revertedWith("withdrawals are paused"); - await setPaused(bridge, signers, 0, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); }); it("withdrawals and deposits are rejected when bridge is paused", async function() { - await setPaused(bridge, signers, 0, PAUSE_WITHDRAWALS_AND_DEPOSITS, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_WITHDRAWALS_AND_DEPOSITS, nextPauseNonce(), validTimestamp()); await expect( bridge.depositERC20(token.address, 1, ethers.utils.parseEther("1.0")) @@ -107,13 +143,13 @@ describe("Deposit & Withdraw ERC20", function() { token, signers, ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, validTimestamp(), recipient, ethers.utils.parseEther("1.0") )).to.be.revertedWith("withdrawals are paused"); - await setPaused(bridge, signers, 0, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); }); it("expired withdrawals are rejected", async function() { @@ -123,7 +159,7 @@ describe("Deposit & Withdraw ERC20", function() { token, signers, ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, expiredTimestamp(), recipient, ethers.utils.parseEther("1.0") @@ -140,8 +176,8 @@ describe("Deposit & Withdraw ERC20", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], - [0, ethers.utils.id("withdrawERC201"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawERC201"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); await expect( @@ -149,7 +185,7 @@ describe("Deposit & Withdraw ERC20", function() { ).to.be.revertedWith("signature does not match"); }); - it("withdrawals with invalid config version are rejected", async function() { + it("withdrawals with invalid domain separator are rejected", async function() { const recipient = signers[1].address; const request = [ ethers.utils.formatBytes32String("0"), @@ -158,9 +194,13 @@ describe("Deposit & Withdraw ERC20", function() { token.address, ethers.utils.parseEther("1.0") ]; + const invalidDomainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint256", "address"], + [(await bridge.version()) + 1, 31337, bridge.address] + )); const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], - [1, ethers.utils.id("withdrawERC20"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], + [invalidDomainSeparator, ethers.utils.id("withdrawERC20"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); await expect( @@ -178,8 +218,8 @@ describe("Deposit & Withdraw ERC20", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], - [0, ethers.utils.id("withdrawERC20"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawERC20"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); signatures[0] = signatures[1]; @@ -198,8 +238,8 @@ describe("Deposit & Withdraw ERC20", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], - [0, ethers.utils.id("withdrawERC20"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawERC20"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); const tmp = signatures[1]; @@ -223,8 +263,8 @@ describe("Deposit & Withdraw ERC20", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], - [0, ethers.utils.id("withdrawERC20"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawERC20"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); await expect( @@ -239,7 +279,7 @@ describe("Deposit & Withdraw ERC20", function() { token, signers, ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, validTimestamp(), signers[2].address, ethers.utils.parseEther("200") @@ -262,7 +302,7 @@ describe("Deposit & Withdraw ERC20", function() { token, signers, ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, validTimestamp(), recipient, ethers.utils.parseEther("1.0") @@ -277,7 +317,7 @@ describe("Deposit & Withdraw ERC20", function() { token, signers, ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, validTimestamp(), signers[2].address, ethers.utils.parseEther("2.0") @@ -286,14 +326,14 @@ describe("Deposit & Withdraw ERC20", function() { }); it("updateSigners invalidates withdrawal transactions", async function() { - await updateSigners(bridge, signers, 0, signers.map(s => s.address), signers.length); + await updateSigners(bridge, signers, domainSeparator, signers.map(s => s.address), signers.length); await expect( withdrawERC20( bridge, token, signers, ethers.utils.formatBytes32String("1"), - 0, + domainSeparator, validTimestamp(), signers[2].address, ethers.utils.parseEther("1.0") diff --git a/solidity/test/eth.js b/solidity/test/eth.js index ce0f403..511fbd0 100644 --- a/solidity/test/eth.js +++ b/solidity/test/eth.js @@ -3,18 +3,24 @@ const { ethers, waffle } = require("hardhat"); const { PAUSE_DEPOSITS, PAUSE_NOTHING, PAUSE_WITHDRAWALS_AND_DEPOSITS, setPaused, nextPauseNonce, PAUSE_WITHDRAWALS } = require("./paused"); const { updateSigners } = require("./updateSigners"); const { validTimestamp, expiredTimestamp } = require("./util"); +const { setDepositAllowed } = require("./setDepositAllowed"); describe("Deposit & Withdraw ETH", function() { let signers; let bridge; + let domainSeparator; + let sender; + this.beforeAll(async function() { signers = (await ethers.getSigners()).slice(0, 20); + sender = signers[0]; signers.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase())); const addresses = signers.map(a => a.address); const Bridge = await ethers.getContractFactory("Bridge"); bridge = await Bridge.deploy(addresses, 20); + domainSeparator = await bridge.domainSeparator(); }); it("fallback function reverts", async function() { @@ -25,10 +31,38 @@ describe("Deposit & Withdraw ETH", function() { await expect(bridge.depositETH(1, {value: 0})).to.be.revertedWith("deposit amount is zero"); }); + it("block deposits of ETH", async function() { + const ERC20 = await ethers.getContractFactory("StellarAsset"); + const token = await ERC20.deploy("Blocked Test Token", "BLOCKED", 18); + await token.mint(sender.address, ethers.utils.parseEther("100.0")); + await token.approve(bridge.address, ethers.utils.parseEther("300.0")); + + await setDepositAllowed(bridge, signers, domainSeparator, token.address, true, 0, validTimestamp()); + expect(await bridge.depositAllowed(token.address)).to.be.true; + + const ethAddress = "0x0000000000000000000000000000000000000000"; + await setDepositAllowed(bridge, signers, domainSeparator, ethAddress, false, 1, validTimestamp()); + expect(await bridge.depositAllowed(ethAddress)).to.be.false; + + const before = await token.balanceOf(bridge.address); + + await bridge.depositERC20( + token.address, 1, ethers.utils.parseEther("1.0") + ); + + const after = await token.balanceOf(bridge.address); + expect(after.sub(before)).to.equal(ethers.utils.parseEther("1.0")); + + await expect(bridge.depositETH(1, {value: ethers.utils.parseEther("1.0")})).to.be.revertedWith("eth deposits are not allowed"); + + await setDepositAllowed(bridge, signers, domainSeparator, ethAddress, true, 2, validTimestamp()); + expect(await bridge.depositAllowed(ethAddress)).to.be.true; + }); + it("deposits are rejected when bridge is paused", async function() { - await setPaused(bridge, signers, 0, PAUSE_DEPOSITS, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_DEPOSITS, nextPauseNonce(), validTimestamp()); await expect(bridge.depositETH(1, {value: ethers.utils.parseEther("1.0")})).to.be.revertedWith("deposits are paused"); - await setPaused(bridge, signers, 0, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); }); it("deposits is successful", async function() { @@ -39,63 +73,63 @@ describe("Deposit & Withdraw ETH", function() { }); it("deposits succeed when withdrawals are paused", async function() { - await setPaused(bridge, signers, 0, PAUSE_WITHDRAWALS, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_WITHDRAWALS, nextPauseNonce(), validTimestamp()); const before = await waffle.provider.getBalance(bridge.address); await bridge.depositETH(1, {value: ethers.utils.parseEther("1.0")}); const after = await waffle.provider.getBalance(bridge.address); expect(after.sub(before)).to.equal(ethers.utils.parseEther("1.0")); - await setPaused(bridge, signers, 0, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); }); - async function withdrawETH(id, configVersion, expiration, recipient, amount) { + async function withdrawETH(id, domainSeparator, expiration, recipient, amount) { const request = [id, expiration, recipient, amount]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, uint256)"], - [configVersion, ethers.utils.id("withdrawETH"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawETH"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); return bridge.withdrawETH(request, signatures, [...Array(20).keys()]); } it("withdrawals are rejected when bridge is paused", async function() { - await setPaused(bridge, signers, 0, PAUSE_WITHDRAWALS, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_WITHDRAWALS, nextPauseNonce(), validTimestamp()); const recipient = signers[1].address; await expect(withdrawETH( ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, validTimestamp(), recipient, ethers.utils.parseEther("1.0") )).to.be.revertedWith("withdrawals are paused"); - await setPaused(bridge, signers, 0, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); }); it("withdrawals and deposits are rejected when bridge is paused", async function() { - await setPaused(bridge, signers, 0, PAUSE_WITHDRAWALS_AND_DEPOSITS, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_WITHDRAWALS_AND_DEPOSITS, nextPauseNonce(), validTimestamp()); await expect(bridge.depositETH(1, {value: ethers.utils.parseEther("1.0")})).to.be.revertedWith("deposits are paused"); const recipient = signers[1].address; await expect(withdrawETH( ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, validTimestamp(), recipient, ethers.utils.parseEther("1.0") )).to.be.revertedWith("withdrawals are paused"); - await setPaused(bridge, signers, 0, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); + await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); }); it("expired withdrawals are rejected", async function() { const recipient = signers[1].address; await expect(withdrawETH( ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, expiredTimestamp(), recipient, ethers.utils.parseEther("1.0") @@ -111,8 +145,8 @@ describe("Deposit & Withdraw ETH", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, uint256)"], - [0, ethers.utils.id("withdrawETH1"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawETH1"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); await expect( @@ -120,7 +154,7 @@ describe("Deposit & Withdraw ETH", function() { ).to.be.revertedWith("signature does not match"); }); - it("withdrawals with invalid config version are rejected", async function() { + it("withdrawals with invalid domain separator are rejected", async function() { const recipient = signers[1].address; const request = [ ethers.utils.formatBytes32String("0"), @@ -128,9 +162,13 @@ describe("Deposit & Withdraw ETH", function() { recipient, ethers.utils.parseEther("1.0") ]; + const invalidDomainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint256", "address"], + [(await bridge.version()) + 1, 31337, bridge.address] + )); const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, uint256)"], - [1, ethers.utils.id("withdrawETH"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, uint256)"], + [invalidDomainSeparator, ethers.utils.id("withdrawETH"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); await expect( @@ -147,8 +185,8 @@ describe("Deposit & Withdraw ETH", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, uint256)"], - [0, ethers.utils.id("withdrawETH"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawETH"), request] ))); const signatures = await Promise.all(signers.slice(0,19).map(s => s.signMessage(hash))); await expect( @@ -165,8 +203,8 @@ describe("Deposit & Withdraw ETH", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, uint256)"], - [0, ethers.utils.id("withdrawETH"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawETH"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); signatures[0] = signatures[1]; @@ -184,8 +222,8 @@ describe("Deposit & Withdraw ETH", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, uint256)"], - [0, ethers.utils.id("withdrawETH"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawETH"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); const tmp = signatures[1]; @@ -208,8 +246,8 @@ describe("Deposit & Withdraw ETH", function() { ethers.utils.parseEther("1.0") ]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(bytes32, uint256, address, uint256)"], - [0, ethers.utils.id("withdrawETH"), request] + ["bytes32", "bytes32", "tuple(bytes32, uint256, address, uint256)"], + [domainSeparator, ethers.utils.id("withdrawETH"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); await expect( @@ -221,7 +259,7 @@ describe("Deposit & Withdraw ETH", function() { await expect( withdrawETH( ethers.utils.formatBytes32String("0"), - 0, + domainSeparator, validTimestamp(), signers[2].address, ethers.utils.parseEther("200") @@ -237,20 +275,20 @@ describe("Deposit & Withdraw ETH", function() { const recipient = signers[1].address; before = await waffle.provider.getBalance(recipient); - await withdrawETH(ethers.utils.formatBytes32String("0"), 0, validTimestamp(), recipient, ethers.utils.parseEther("1.0")); + await withdrawETH(ethers.utils.formatBytes32String("0"), domainSeparator, validTimestamp(), recipient, ethers.utils.parseEther("1.0")); after = await waffle.provider.getBalance(recipient); expect(after.sub(before)).to.equal(ethers.utils.parseEther("1.0")); // reusing request id will be rejected await expect( - withdrawETH(ethers.utils.formatBytes32String("0"), 0, validTimestamp(), signers[2].address, ethers.utils.parseEther("2.0")) + withdrawETH(ethers.utils.formatBytes32String("0"), domainSeparator, validTimestamp(), signers[2].address, ethers.utils.parseEther("2.0")) ).to.be.revertedWith("request is already fulfilled"); }); it("updateSigners invalidates withdrawal transactions", async function() { - await updateSigners(bridge, signers, 0, signers.map(s => s.address), signers.length); + await updateSigners(bridge, signers, domainSeparator, signers.map(s => s.address), signers.length); await expect( - withdrawETH(ethers.utils.formatBytes32String("1"), 0, validTimestamp(), signers[2].address, ethers.utils.parseEther("1.0")) + withdrawETH(ethers.utils.formatBytes32String("1"), domainSeparator, validTimestamp(), signers[2].address, ethers.utils.parseEther("1.0")) ).to.be.revertedWith("signature does not match"); }); }); \ No newline at end of file diff --git a/solidity/test/paused.js b/solidity/test/paused.js index 7a27642..e30cad2 100644 --- a/solidity/test/paused.js +++ b/solidity/test/paused.js @@ -14,11 +14,11 @@ function nextPauseNonce() { return pausedNonce++; } -async function setPaused(bridge, signers, configVersion, state, nonce, expiration) { +async function setPaused(bridge, signers, domainSeparator, state, nonce, expiration) { const request = [state, nonce, expiration]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [configVersion, ethers.utils.id("setPaused"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setPaused"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); return bridge.setPaused(request, signatures, [...Array(signers.length).keys()]); @@ -27,6 +27,7 @@ async function setPaused(bridge, signers, configVersion, state, nonce, expiratio describe("setPaused", function() { let signers; let bridge; + let domainSeparator; this.beforeAll(async function() { signers = (await ethers.getSigners()).slice(0, 20); @@ -35,25 +36,26 @@ describe("setPaused", function() { const Bridge = await ethers.getContractFactory("Bridge"); bridge = await Bridge.deploy(addresses, 20); + domainSeparator = await bridge.domainSeparator(); }); it("is rejected if paused bitmask is invalid", async function() { await expect( - setPaused(bridge, signers, 0, 4, nextPauseNonce(), validTimestamp()) + setPaused(bridge, signers, domainSeparator, 4, nextPauseNonce(), validTimestamp()) ).to.be.revertedWith("invalid paused value"); }); it("is rejected if expired", async function() { await expect( - setPaused(bridge, signers, 0, PAUSE_DEPOSITS, nextPauseNonce(), expiredTimestamp()) + setPaused(bridge, signers, domainSeparator, PAUSE_DEPOSITS, nextPauseNonce(), expiredTimestamp()) ).to.be.revertedWith("request is expired"); }); it("is rejected if method id is invalid", async function() { const request = [PAUSE_DEPOSITS, 0, validTimestamp()]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [0, ethers.utils.id("setPaused1"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setPaused1"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); await expect( @@ -61,11 +63,15 @@ describe("setPaused", function() { ).to.be.revertedWith("signature does not match"); }); - it("is rejected if config version is invalid", async function() { + it("is rejected if domain separator is invalid", async function() { const request = [PAUSE_DEPOSITS, 0, validTimestamp()]; + const invalidDomainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint256", "address"], + [(await bridge.version()) + 1, 31337, bridge.address] + )); const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [1, ethers.utils.id("setPaused"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [invalidDomainSeparator, ethers.utils.id("setPaused"), request] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); await expect( @@ -76,8 +82,8 @@ describe("setPaused", function() { it("is rejected if there are too few signatures", async function() { const request = [PAUSE_DEPOSITS, 0, validTimestamp()]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [0, ethers.utils.id("setPaused"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setPaused"), request] ))); const signatures = await Promise.all(signers.slice(0,19).map(s => s.signMessage(hash))); await expect( @@ -88,8 +94,8 @@ describe("setPaused", function() { it("is rejected if there are invalid signatures", async function() { const request = [PAUSE_DEPOSITS, 0, validTimestamp()]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [0, ethers.utils.id("setPaused"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setPaused"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); signatures[0] = signatures[1]; @@ -101,8 +107,8 @@ describe("setPaused", function() { it("is rejected if the signatures are not sorted", async function() { const request = [PAUSE_DEPOSITS, 0, validTimestamp()]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [0, ethers.utils.id("setPaused"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setPaused"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); const tmp = signatures[1]; @@ -119,8 +125,8 @@ describe("setPaused", function() { it("is rejected if the indexes length does not match signatures length", async function() { const request = [PAUSE_DEPOSITS, 0, validTimestamp()]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [0, ethers.utils.id("setPaused"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setPaused"), request] ))); const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); await expect( @@ -131,8 +137,8 @@ describe("setPaused", function() { it("succeeds", async function() { let request = [PAUSE_DEPOSITS, 0, validTimestamp()]; let hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [0, ethers.utils.id("setPaused"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setPaused"), request] ))); let signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); await bridge.setPaused(request, signatures, [...Array(20).keys()]); @@ -144,12 +150,12 @@ describe("setPaused", function() { }); it("updateSigners invalidates setPaused transactions", async function() { - await updateSigners(bridge, signers, 0, signers.map(s => s.address), signers.length); + await updateSigners(bridge, signers, domainSeparator, signers.map(s => s.address), signers.length); let request = [PAUSE_NOTHING, 0, validTimestamp()]; let hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "tuple(uint8, uint256, uint256)"], - [0, ethers.utils.id("setPaused"), request] + ["bytes32", "bytes32", "tuple(uint8, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setPaused"), request] ))); let signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); await expect( diff --git a/solidity/test/registerStellarAsset.js b/solidity/test/registerStellarAsset.js index a79efd5..0ce229a 100644 --- a/solidity/test/registerStellarAsset.js +++ b/solidity/test/registerStellarAsset.js @@ -12,12 +12,12 @@ describe("registerStellarAsset", function() { let wrappedXLM; let recipient; - async function registerStellarAsset(configVersion, name, symbol, decimals) { + async function registerStellarAsset(domainSeparator, name, symbol, decimals) { const request = [decimals, name, symbol]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "uint8", "bytes32", "bytes32"], + ["bytes32", "bytes32", "uint8", "bytes32", "bytes32"], [ - configVersion, + domainSeparator, ethers.utils.id("registerStellarAsset"), decimals, ethers.utils.id(name), @@ -44,27 +44,28 @@ describe("registerStellarAsset", function() { const Bridge = await ethers.getContractFactory("Bridge"); bridge = await Bridge.deploy(addresses, 20); - wrappedXLM = await getToken(await registerStellarAsset(0, "Stellar Lumens", "XLM", 7)); + wrappedXLM = await getToken(await registerStellarAsset(await bridge.domainSeparator(), "Stellar Lumens", "XLM", 7)); expect(await wrappedXLM.decimals()).to.be.eql(7); expect(await wrappedXLM.name()).to.be.eql("Stellar Lumens"); expect(await wrappedXLM.symbol()).to.be.eql("XLM"); expect(await bridge.isStellarAsset(wrappedXLM.address)).to.be.true; + expect(await bridge.depositAllowed(wrappedXLM.address)).to.be.true; }); it("rejects duplicate transactions", async function() { - let version = await bridge.version(); - await expect(registerStellarAsset(version, "Stellar Lumens", "XLM", 7)).to.be.reverted; - await updateSigners(bridge, signers, version, signers.map(s => s.address), signers.length); - version = await bridge.version(); - await expect(registerStellarAsset(1, "Stellar Lumens", "XLM", 7)).to.be.reverted; + let domainSeparator = await bridge.domainSeparator(); + await expect(registerStellarAsset(domainSeparator, "Stellar Lumens", "XLM", 7)).to.be.reverted; + await updateSigners(bridge, signers, domainSeparator, signers.map(s => s.address), signers.length); + domainSeparator = await bridge.domainSeparator(); + await expect(registerStellarAsset(domainSeparator, "Stellar Lumens", "XLM", 7)).to.be.reverted; }); it("updateSigners invalidates transactions", async function() { - let version = await bridge.version(); - await updateSigners(bridge, signers, version, signers.map(s => s.address), signers.length); - await expect(registerStellarAsset(version, "wrapped yXLM", "yXLM", 7)).to.be.reverted; - version = await bridge.version(); - await expect(registerStellarAsset(version, "wrapped yXLM", "yXLM", 7)).to.not.be.reverted; + let domainSeparator = await bridge.domainSeparator(); + await updateSigners(bridge, signers, domainSeparator, signers.map(s => s.address), signers.length); + await expect(registerStellarAsset(domainSeparator, "wrapped yXLM", "yXLM", 7)).to.be.reverted; + domainSeparator = await bridge.domainSeparator(); + await expect(registerStellarAsset(domainSeparator, "wrapped yXLM", "yXLM", 7)).to.not.be.reverted; }); it("StellarAssets.mint() cannot be called", async function() { @@ -81,7 +82,7 @@ describe("registerStellarAsset", function() { wrappedXLM, signers, ethers.utils.formatBytes32String("0"), - await bridge.version(), + await bridge.domainSeparator(), validTimestamp(), recipient.address, ethers.utils.parseEther("3.0"), @@ -121,9 +122,9 @@ describe("registerStellarAsset", function() { const symbol = "USDC"; const request = [decimals, name, symbol]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "uint8", "bytes32", "bytes32"], + ["bytes32", "bytes32", "uint8", "bytes32", "bytes32"], [ - await bridge.version(), + await bridge.domainSeparator(), ethers.utils.id("registerStellarAsset1"), decimals, ethers.utils.id(name), @@ -136,15 +137,40 @@ describe("registerStellarAsset", function() { ).revertedWith("signature does not match"); }); + it("transactions with invalid domain separator are rejected", async function() { + const decimals = 7; + const name = "Wrapped USDC"; + const symbol = "USDC"; + const request = [decimals, name, symbol]; + const invalidDomainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint256", "address"], + [(await bridge.version()) + 1, 31337, bridge.address] + )); + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "uint8", "bytes32", "bytes32"], + [ + invalidDomainSeparator, + ethers.utils.id("registerStellarAsset"), + decimals, + ethers.utils.id(name), + ethers.utils.id(symbol), + ] + ))); + const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); + await expect( + bridge.registerStellarAsset(request, signatures, [...Array(20).keys()]) + ).revertedWith("signature does not match"); + }); + it("transactions with invalid signatures are rejected", async function() { const decimals = 7; const name = "Wrapped USDC"; const symbol = "USDC"; const request = [decimals, name, symbol]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "uint8", "bytes32", "bytes32"], + ["bytes32", "bytes32", "uint8", "bytes32", "bytes32"], [ - await bridge.version(), + await bridge.domainSeparator(), ethers.utils.id("registerStellarAsset"), decimals, ethers.utils.id(name), @@ -164,9 +190,9 @@ describe("registerStellarAsset", function() { const symbol = "USDC"; const request = [decimals, name, symbol]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "uint8", "bytes32", "bytes32"], + ["bytes32", "bytes32", "uint8", "bytes32", "bytes32"], [ - await bridge.version(), + await bridge.domainSeparator(), ethers.utils.id("registerStellarAsset"), decimals, ethers.utils.id(name), @@ -191,9 +217,9 @@ describe("registerStellarAsset", function() { const symbol = "USDC"; const request = [decimals, name, symbol]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "uint8", "bytes32", "bytes32"], + ["bytes32", "bytes32", "uint8", "bytes32", "bytes32"], [ - await bridge.version(), + await bridge.domainSeparator(), ethers.utils.id("registerStellarAsset"), decimals, ethers.utils.id(name), diff --git a/solidity/test/setDepositAllowed.js b/solidity/test/setDepositAllowed.js new file mode 100644 index 0000000..85232a4 --- /dev/null +++ b/solidity/test/setDepositAllowed.js @@ -0,0 +1,153 @@ +const { expect } = require("chai"); +const { ethers } = require("hardhat"); +const { expiredTimestamp, validTimestamp } = require("./util"); +const { updateSigners } = require("./updateSigners"); + +async function setDepositAllowed(bridge, signers, domainSeparator, token, allowed, nonce, expiration) { + const request = [token, allowed, nonce, expiration]; + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setDepositAllowed"), request] + ))); + const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); + return bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]); +} + +describe("setDepositAllowed", function() { + let signers; + let bridge; + let domainSeparator; + const ethAddress = "0x0000000000000000000000000000000000000000"; + + this.beforeAll(async function() { + signers = (await ethers.getSigners()).slice(0, 20); + signers.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase())); + const addresses = signers.map(a => a.address); + + const Bridge = await ethers.getContractFactory("Bridge"); + bridge = await Bridge.deploy(addresses, 20); + domainSeparator = await bridge.domainSeparator(); + }); + + it("is rejected if expired", async function() { + await expect( + setDepositAllowed(bridge, signers, domainSeparator, ethAddress, false, 0, expiredTimestamp()) + ).to.be.revertedWith("request is expired"); + }); + + it("is rejected if method id is invalid", async function() { + const request = [ethAddress, false, 0, validTimestamp()]; + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setDepositAllowed1"), request] + ))); + const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); + await expect( + bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]) + ).to.be.revertedWith("signature does not match"); + }); + + it("is rejected if domain separator is invalid", async function() { + const invalidDomainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint256", "address"], + [(await bridge.version())+1, 31337, bridge.address] + )); + const request = [ethAddress, false, 0, validTimestamp()]; + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [invalidDomainSeparator, ethers.utils.id("setDepositAllowed"), request] + ))); + const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); + await expect( + bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]) + ).to.be.revertedWith("signature does not match"); + }); + + it("is rejected if there are too few signatures", async function() { + const request = [ethAddress, false, 0, validTimestamp()]; + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setDepositAllowed"), request] + ))); + const signatures = await Promise.all(signers.slice(0,19).map(s => s.signMessage(hash))); + await expect( + bridge.setDepositAllowed(request, signatures, [...Array(signatures.length).keys()]) + ).to.be.revertedWith("not enough signatures"); + }); + + it("is rejected if there are invalid signatures", async function() { + const request = [ethAddress, false, 0, validTimestamp()]; + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setDepositAllowed"), request] + ))); + const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); + signatures[0] = signatures[1]; + await expect( + bridge.setDepositAllowed(request, signatures, [...Array(signatures.length).keys()]) + ).to.be.revertedWith("signature does not match"); + }); + + it("is rejected if the signatures are not sorted", async function() { + const request = [ethAddress, false, 0, validTimestamp()]; + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setDepositAllowed"), request] + ))); + const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); + const tmp = signatures[1]; + signatures[1] = signatures[0]; + signatures[0] = tmp; + const indexes = [...Array(20).keys()]; + indexes[0] = 1; + indexes[1] = 0; + await expect( + bridge.setDepositAllowed(request, signatures, indexes) + ).to.be.revertedWith("signatures not sorted by signer"); + }); + + it("is rejected if the indexes length does not match signatures length", async function() { + const request = [ethAddress, false, 0, validTimestamp()]; + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setDepositAllowed"), request] + ))); + const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); + await expect( + bridge.setDepositAllowed(request, signatures, [...Array(19).keys()]) + ).to.be.revertedWith("number of signatures does not equal number of indexes"); + }); + + it("nonce prevents transaction replay", async function() { + const request = [ethAddress, false, 0, validTimestamp()]; + let hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setDepositAllowed"), request] + ))); + let signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); + await bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]); + + // reusing transaction will be rejected + await expect( + bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]) + ).to.be.revertedWith("request is already fulfilled"); + }); + + it("updateSigners invalidates setDepositAllowed transactions", async function() { + await updateSigners(bridge, signers, domainSeparator, signers.map(s => s.address), signers.length); + + const request = [ethAddress, true, 1, validTimestamp()]; + let hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], + [domainSeparator, ethers.utils.id("setDepositAllowed"), request] + ))); + let signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); + await expect( + bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]) + ).to.be.revertedWith("signature does not match"); + }); +}); + +module.exports = { + setDepositAllowed, +}; \ No newline at end of file diff --git a/solidity/test/updateSigners.js b/solidity/test/updateSigners.js index f1534a5..7c3c094 100644 --- a/solidity/test/updateSigners.js +++ b/solidity/test/updateSigners.js @@ -2,10 +2,10 @@ const { expect } = require("chai"); const { ethers } = require("hardhat"); -async function updateSigners(bridge, signers, configVersion, newAddresses, newMinThreshold) { +async function updateSigners(bridge, signers, domainSeparator, newAddresses, newMinThreshold) { const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "address[]", "uint8"], - [configVersion, ethers.utils.id("updateSigners"), newAddresses, newMinThreshold] + ["bytes32", "bytes32", "address[]", "uint8"], + [domainSeparator, ethers.utils.id("updateSigners"), newAddresses, newMinThreshold] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); return bridge.updateSigners(newAddresses, newMinThreshold, signatures, [...Array(signers.length).keys()]); @@ -26,28 +26,47 @@ describe("updateSigners", function() { it("rejects invalid minThreshold values", async function() { const addresses = signers.map(a => a.address); + let domainSeparator = await bridge.domainSeparator(); for (let i = 0; i <= 10; i++) { - await expect(updateSigners(bridge, signers, 0, addresses, i)).to.be.revertedWith("min threshold is too low"); + await expect(updateSigners(bridge, signers, domainSeparator, addresses, i)).to.be.revertedWith("min threshold is too low"); } - await expect(updateSigners(bridge, signers, 0, addresses, 21)).to.be.revertedWith("min threshold is too high"); + await expect(updateSigners(bridge, signers, domainSeparator, addresses, 21)).to.be.revertedWith("min threshold is too high"); let version = await bridge.version(); for (let i = 11; i <= 20; i++) { - await updateSigners(bridge, signers, version, addresses, i); + await updateSigners(bridge, signers, domainSeparator, addresses, i); version++; expect(await bridge.minThreshold()).to.equal(i); expect(await bridge.version()).to.equal(version); + expect(await bridge.domainSeparator()).to.not.equal(domainSeparator); + domainSeparator = await bridge.domainSeparator() } }); it("rejects invalid method id", async function() { const addresses = signers.map(a => a.address); - const version = await bridge.version(); + const domainSeparator = await bridge.domainSeparator(); const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "address[]", "uint8"], - [version, ethers.utils.id("updateSigners1"), addresses, 20] + ["bytes32", "bytes32", "address[]", "uint8"], + [domainSeparator, ethers.utils.id("updateSigners1"), addresses, 20] + ))); + const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); + await expect(bridge.updateSigners( + addresses, 20, signatures, [...Array(signers.length).keys()] + )).to.be.revertedWith("signature does not match"); + }); + + it("rejects invalid domain separator", async function() { + const addresses = signers.map(a => a.address); + const invalidDomainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint256", "address"], + [(await bridge.version()) + 1, 31337, bridge.address] + )); + const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( + ["bytes32", "bytes32", "address[]", "uint8"], + [invalidDomainSeparator, ethers.utils.id("updateSigners"), addresses, 20] ))); const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); await expect(bridge.updateSigners( @@ -62,17 +81,17 @@ describe("updateSigners", function() { } newSigners.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase())); const addresses = newSigners.map(a => a.address); - const version = await bridge.version(); + const domainSeparator = await bridge.domainSeparator(); await expect(updateSigners( - bridge, signers, version, [addresses[0], addresses[0], addresses[1]], 3 + bridge, signers, domainSeparator, [addresses[0], addresses[0], addresses[1]], 3 )).to.be.revertedWith("signers not sorted"); await expect(updateSigners( - bridge, signers, version, [addresses[0], addresses[1], addresses[2], addresses[4], addresses[3]], 5 + bridge, signers, domainSeparator, [addresses[0], addresses[1], addresses[2], addresses[4], addresses[3]], 5 )).to.be.revertedWith("signers not sorted"); - await expect(updateSigners(bridge, signers, version, [], 0)).to.be.revertedWith("too few signers"); - await expect(updateSigners(bridge, signers, version, addresses, 255)).to.be.revertedWith("too many signers"); + await expect(updateSigners(bridge, signers, domainSeparator, [], 0)).to.be.revertedWith("too few signers"); + await expect(updateSigners(bridge, signers, domainSeparator, addresses, 255)).to.be.revertedWith("too many signers"); }); it("succeeds", async function() { @@ -82,13 +101,13 @@ describe("updateSigners", function() { } newSigners.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase())); const addresses = newSigners.map(a => a.address); - const version = await bridge.version(); + const domainSeparator = await bridge.domainSeparator(); - await updateSigners(bridge, signers, version, addresses, addresses.length); + await updateSigners(bridge, signers, domainSeparator, addresses, addresses.length); // replay prevention await expect(updateSigners( - bridge, signers, version, addresses, addresses.length + bridge, signers, domainSeparator, addresses, addresses.length )).to.be.revertedWith("signature does not match"); for(let i = 0; i < addresses.length; i++) { From 6c4dd3b3676847e5690abb6874e4068b2ae18257 Mon Sep 17 00:00:00 2001 From: tamirms Date: Thu, 8 Sep 2022 12:03:52 +0200 Subject: [PATCH 2/7] generate new go bindings --- solidity-go/bridge.go | 249 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 238 insertions(+), 11 deletions(-) diff --git a/solidity-go/bridge.go b/solidity-go/bridge.go index 2918c30..742a475 100644 --- a/solidity-go/bridge.go +++ b/solidity-go/bridge.go @@ -35,6 +35,14 @@ type RegisterStellarAssetRequest struct { Symbol string } +// SetDepositAllowedRequest is an auto generated low-level Go binding around an user-defined struct. +type SetDepositAllowedRequest struct { + Token common.Address + Allowed bool + Nonce *big.Int + Expiration *big.Int +} + // SetPausedRequest is an auto generated low-level Go binding around an user-defined struct. type SetPausedRequest struct { Value uint8 @@ -61,7 +69,7 @@ type WithdrawETHRequest struct { // BridgeMetaData contains all meta data concerning the Bridge contract. var BridgeMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_minThreshold\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"minThreshold\",\"type\":\"uint8\"}],\"name\":\"RegisterSigners\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"RegisterStellarAsset\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"value\",\"type\":\"uint8\"}],\"name\":\"SetPaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"depositERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"}],\"name\":\"depositETH\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isStellarAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minThreshold\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"symbol\",\"type\":\"string\"}],\"internalType\":\"structRegisterStellarAssetRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"registerStellarAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestID\",\"type\":\"bytes32\"}],\"name\":\"requestStatus\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"value\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"internalType\":\"structSetPausedRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"setPaused\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"signers\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_minThreshold\",\"type\":\"uint8\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"updateSigners\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structWithdrawERC20Request\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"withdrawERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structWithdrawETHRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"withdrawETH\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_minThreshold\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"domainSeparator\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"minThreshold\",\"type\":\"uint8\"}],\"name\":\"RegisterSigners\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"RegisterStellarAsset\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"SetDepositAllowed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"value\",\"type\":\"uint8\"}],\"name\":\"SetPaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"depositAllowed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"depositERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"}],\"name\":\"depositETH\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"domainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isStellarAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minThreshold\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"symbol\",\"type\":\"string\"}],\"internalType\":\"structRegisterStellarAssetRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"registerStellarAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestID\",\"type\":\"bytes32\"}],\"name\":\"requestStatus\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"internalType\":\"structSetDepositAllowedRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"setDepositAllowed\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"value\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"internalType\":\"structSetPausedRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"setPaused\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"signers\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_minThreshold\",\"type\":\"uint8\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"updateSigners\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structWithdrawERC20Request\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"withdrawERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structWithdrawETHRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"withdrawETH\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", } // BridgeABI is the input ABI used to generate the binding from. @@ -210,6 +218,68 @@ func (_Bridge *BridgeTransactorRaw) Transact(opts *bind.TransactOpts, method str return _Bridge.Contract.contract.Transact(opts, method, params...) } +// DepositAllowed is a free data retrieval call binding the contract method 0xbd9f9b47. +// +// Solidity: function depositAllowed(address ) view returns(bool) +func (_Bridge *BridgeCaller) DepositAllowed(opts *bind.CallOpts, arg0 common.Address) (bool, error) { + var out []interface{} + err := _Bridge.contract.Call(opts, &out, "depositAllowed", arg0) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// DepositAllowed is a free data retrieval call binding the contract method 0xbd9f9b47. +// +// Solidity: function depositAllowed(address ) view returns(bool) +func (_Bridge *BridgeSession) DepositAllowed(arg0 common.Address) (bool, error) { + return _Bridge.Contract.DepositAllowed(&_Bridge.CallOpts, arg0) +} + +// DepositAllowed is a free data retrieval call binding the contract method 0xbd9f9b47. +// +// Solidity: function depositAllowed(address ) view returns(bool) +func (_Bridge *BridgeCallerSession) DepositAllowed(arg0 common.Address) (bool, error) { + return _Bridge.Contract.DepositAllowed(&_Bridge.CallOpts, arg0) +} + +// DomainSeparator is a free data retrieval call binding the contract method 0xf698da25. +// +// Solidity: function domainSeparator() view returns(bytes32) +func (_Bridge *BridgeCaller) DomainSeparator(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _Bridge.contract.Call(opts, &out, "domainSeparator") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// DomainSeparator is a free data retrieval call binding the contract method 0xf698da25. +// +// Solidity: function domainSeparator() view returns(bytes32) +func (_Bridge *BridgeSession) DomainSeparator() ([32]byte, error) { + return _Bridge.Contract.DomainSeparator(&_Bridge.CallOpts) +} + +// DomainSeparator is a free data retrieval call binding the contract method 0xf698da25. +// +// Solidity: function domainSeparator() view returns(bytes32) +func (_Bridge *BridgeCallerSession) DomainSeparator() ([32]byte, error) { + return _Bridge.Contract.DomainSeparator(&_Bridge.CallOpts) +} + // IsStellarAsset is a free data retrieval call binding the contract method 0x453c6d97. // // Solidity: function isStellarAsset(address ) view returns(bool) @@ -460,6 +530,27 @@ func (_Bridge *BridgeTransactorSession) RegisterStellarAsset(request RegisterSte return _Bridge.Contract.RegisterStellarAsset(&_Bridge.TransactOpts, request, signatures, indexes) } +// SetDepositAllowed is a paid mutator transaction binding the contract method 0x73899162. +// +// Solidity: function setDepositAllowed((address,bool,uint256,uint256) request, bytes[] signatures, uint8[] indexes) returns() +func (_Bridge *BridgeTransactor) SetDepositAllowed(opts *bind.TransactOpts, request SetDepositAllowedRequest, signatures [][]byte, indexes []uint8) (*types.Transaction, error) { + return _Bridge.contract.Transact(opts, "setDepositAllowed", request, signatures, indexes) +} + +// SetDepositAllowed is a paid mutator transaction binding the contract method 0x73899162. +// +// Solidity: function setDepositAllowed((address,bool,uint256,uint256) request, bytes[] signatures, uint8[] indexes) returns() +func (_Bridge *BridgeSession) SetDepositAllowed(request SetDepositAllowedRequest, signatures [][]byte, indexes []uint8) (*types.Transaction, error) { + return _Bridge.Contract.SetDepositAllowed(&_Bridge.TransactOpts, request, signatures, indexes) +} + +// SetDepositAllowed is a paid mutator transaction binding the contract method 0x73899162. +// +// Solidity: function setDepositAllowed((address,bool,uint256,uint256) request, bytes[] signatures, uint8[] indexes) returns() +func (_Bridge *BridgeTransactorSession) SetDepositAllowed(request SetDepositAllowedRequest, signatures [][]byte, indexes []uint8) (*types.Transaction, error) { + return _Bridge.Contract.SetDepositAllowed(&_Bridge.TransactOpts, request, signatures, indexes) +} + // SetPaused is a paid mutator transaction binding the contract method 0xfac7d40f. // // Solidity: function setPaused((uint8,uint256,uint256) request, bytes[] signatures, uint8[] indexes) returns() @@ -750,15 +841,16 @@ func (it *BridgeRegisterSignersIterator) Close() error { // BridgeRegisterSigners represents a RegisterSigners event raised by the Bridge contract. type BridgeRegisterSigners struct { - Version *big.Int - Signers []common.Address - MinThreshold uint8 - Raw types.Log // Blockchain specific contextual infos + Version *big.Int + DomainSeparator [32]byte + Signers []common.Address + MinThreshold uint8 + Raw types.Log // Blockchain specific contextual infos } -// FilterRegisterSigners is a free log retrieval operation binding the contract event 0x5d291071bb8cb02c25b56ac7a404864bbf1b404850664208dc12952b59017a8a. +// FilterRegisterSigners is a free log retrieval operation binding the contract event 0x8efb61e94bf9ccfbdcc92531bc3c176ce376859434de722bec5b6e0813df9f2c. // -// Solidity: event RegisterSigners(uint256 version, address[] signers, uint8 minThreshold) +// Solidity: event RegisterSigners(uint256 version, bytes32 domainSeparator, address[] signers, uint8 minThreshold) func (_Bridge *BridgeFilterer) FilterRegisterSigners(opts *bind.FilterOpts) (*BridgeRegisterSignersIterator, error) { logs, sub, err := _Bridge.contract.FilterLogs(opts, "RegisterSigners") @@ -768,9 +860,9 @@ func (_Bridge *BridgeFilterer) FilterRegisterSigners(opts *bind.FilterOpts) (*Br return &BridgeRegisterSignersIterator{contract: _Bridge.contract, event: "RegisterSigners", logs: logs, sub: sub}, nil } -// WatchRegisterSigners is a free log subscription operation binding the contract event 0x5d291071bb8cb02c25b56ac7a404864bbf1b404850664208dc12952b59017a8a. +// WatchRegisterSigners is a free log subscription operation binding the contract event 0x8efb61e94bf9ccfbdcc92531bc3c176ce376859434de722bec5b6e0813df9f2c. // -// Solidity: event RegisterSigners(uint256 version, address[] signers, uint8 minThreshold) +// Solidity: event RegisterSigners(uint256 version, bytes32 domainSeparator, address[] signers, uint8 minThreshold) func (_Bridge *BridgeFilterer) WatchRegisterSigners(opts *bind.WatchOpts, sink chan<- *BridgeRegisterSigners) (event.Subscription, error) { logs, sub, err := _Bridge.contract.WatchLogs(opts, "RegisterSigners") @@ -805,9 +897,9 @@ func (_Bridge *BridgeFilterer) WatchRegisterSigners(opts *bind.WatchOpts, sink c }), nil } -// ParseRegisterSigners is a log parse operation binding the contract event 0x5d291071bb8cb02c25b56ac7a404864bbf1b404850664208dc12952b59017a8a. +// ParseRegisterSigners is a log parse operation binding the contract event 0x8efb61e94bf9ccfbdcc92531bc3c176ce376859434de722bec5b6e0813df9f2c. // -// Solidity: event RegisterSigners(uint256 version, address[] signers, uint8 minThreshold) +// Solidity: event RegisterSigners(uint256 version, bytes32 domainSeparator, address[] signers, uint8 minThreshold) func (_Bridge *BridgeFilterer) ParseRegisterSigners(log types.Log) (*BridgeRegisterSigners, error) { event := new(BridgeRegisterSigners) if err := _Bridge.contract.UnpackLog(event, "RegisterSigners", log); err != nil { @@ -951,6 +1043,141 @@ func (_Bridge *BridgeFilterer) ParseRegisterStellarAsset(log types.Log) (*Bridge return event, nil } +// BridgeSetDepositAllowedIterator is returned from FilterSetDepositAllowed and is used to iterate over the raw logs and unpacked data for SetDepositAllowed events raised by the Bridge contract. +type BridgeSetDepositAllowedIterator struct { + Event *BridgeSetDepositAllowed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BridgeSetDepositAllowedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BridgeSetDepositAllowed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BridgeSetDepositAllowed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BridgeSetDepositAllowedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BridgeSetDepositAllowedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BridgeSetDepositAllowed represents a SetDepositAllowed event raised by the Bridge contract. +type BridgeSetDepositAllowed struct { + Token common.Address + Allowed bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSetDepositAllowed is a free log retrieval operation binding the contract event 0x4db541bbea3efdaa1d5156f38b6874e3952f2173563fe2975acbcee62cee9ca0. +// +// Solidity: event SetDepositAllowed(address token, bool allowed) +func (_Bridge *BridgeFilterer) FilterSetDepositAllowed(opts *bind.FilterOpts) (*BridgeSetDepositAllowedIterator, error) { + + logs, sub, err := _Bridge.contract.FilterLogs(opts, "SetDepositAllowed") + if err != nil { + return nil, err + } + return &BridgeSetDepositAllowedIterator{contract: _Bridge.contract, event: "SetDepositAllowed", logs: logs, sub: sub}, nil +} + +// WatchSetDepositAllowed is a free log subscription operation binding the contract event 0x4db541bbea3efdaa1d5156f38b6874e3952f2173563fe2975acbcee62cee9ca0. +// +// Solidity: event SetDepositAllowed(address token, bool allowed) +func (_Bridge *BridgeFilterer) WatchSetDepositAllowed(opts *bind.WatchOpts, sink chan<- *BridgeSetDepositAllowed) (event.Subscription, error) { + + logs, sub, err := _Bridge.contract.WatchLogs(opts, "SetDepositAllowed") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BridgeSetDepositAllowed) + if err := _Bridge.contract.UnpackLog(event, "SetDepositAllowed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSetDepositAllowed is a log parse operation binding the contract event 0x4db541bbea3efdaa1d5156f38b6874e3952f2173563fe2975acbcee62cee9ca0. +// +// Solidity: event SetDepositAllowed(address token, bool allowed) +func (_Bridge *BridgeFilterer) ParseSetDepositAllowed(log types.Log) (*BridgeSetDepositAllowed, error) { + event := new(BridgeSetDepositAllowed) + if err := _Bridge.contract.UnpackLog(event, "SetDepositAllowed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // BridgeSetPausedIterator is returned from FilterSetPaused and is used to iterate over the raw logs and unpacked data for SetPaused events raised by the Bridge contract. type BridgeSetPausedIterator struct { Event *BridgeSetPaused // Event containing the contract specifics and raw log From 63d93f03bfc08f7da401c413117262d2d3356385 Mon Sep 17 00:00:00 2001 From: tamirms Date: Thu, 8 Sep 2022 16:06:43 +0200 Subject: [PATCH 3/7] update validator to use domain separator --- app/app.go | 16 ++++++++++------ app/config_test.go | 21 ++++++++++----------- app/testdata/example.cfg | 1 - ethereum/observer.go | 6 ++++++ ethereum/signer.go | 23 +++++++++++------------ ethereum/signer_test.go | 6 +++--- integration/integration.go | 23 +++++++++++------------ 7 files changed, 51 insertions(+), 45 deletions(-) diff --git a/app/app.go b/app/app.go index a820235..5ae4bfd 100644 --- a/app/app.go +++ b/app/app.go @@ -50,10 +50,9 @@ type Config struct { StellarBridgeAccount string `toml:"stellar_bridge_account" valid:"stellar_accountid"` StellarPrivateKey string `toml:"stellar_private_key" valid:"stellar_seed"` - EthereumRPCURL string `toml:"ethereum_rpc_url" valid:"-"` - EthereumBridgeAddress string `toml:"ethereum_bridge_address" valid:"-"` - EthereumBridgeConfigVersion uint32 `toml:"ethereum_bridge_config_version" valid:"-"` - EthereumPrivateKey string `toml:"ethereum_private_key" valid:"-"` + EthereumRPCURL string `toml:"ethereum_rpc_url" valid:"-"` + EthereumBridgeAddress string `toml:"ethereum_bridge_address" valid:"-"` + EthereumPrivateKey string `toml:"ethereum_private_key" valid:"-"` AssetMapping []backend.AssetMappingConfigEntry `toml:"asset_mapping" valid:"-"` @@ -147,10 +146,15 @@ func (a *App) initWorker(config Config, client *horizonclient.Client, ethObserve converter, err := backend.NewAssetConverter(config.AssetMapping) if err != nil { - log.Fatal("unable to create asset converter", err) + log.Fatalf("unable to create asset converter: %v", err) + } + + domainSeparator, err := ethObserver.GetDomainSeparator(a.appCtx) + if err != nil { + log.Fatalf("unable to fetch domain separator: %v", err) } - ethSigner, err := ethereum.NewSigner(config.EthereumPrivateKey, config.EthereumBridgeConfigVersion) + ethSigner, err := ethereum.NewSigner(config.EthereumPrivateKey, domainSeparator) if err != nil { log.Fatalf("cannot create ethereum signer: %v", err) } diff --git a/app/config_test.go b/app/config_test.go index 83e1d2f..03b32ca 100644 --- a/app/config_test.go +++ b/app/config_test.go @@ -15,17 +15,16 @@ func TestParseConfig(t *testing.T) { require.NoError(t, err) expected := Config{ - Port: 8000, - AdminPort: 6060, - PostgresDSN: "dbname=starbridge user=starbridge", - HorizonURL: "https://horizon-testnet.stellar.org", - NetworkPassphrase: "Test SDF Network ; September 2015", - StellarBridgeAccount: "GAJKCRY6CIOXRIVK55ALOOJA327XN4JZ5KKN7YCTT3WM5W6BMFXMVQC2", - StellarPrivateKey: "SCSTO3PMPM2BNLR2MYKVHWCJ2FNHQGFWKPOFH6UX4N3HO6HMK4JBSJ6F", - EthereumRPCURL: "https://ethereum-goerli-rpc.allthatnode.com", - EthereumBridgeAddress: "0xD0675839A6C2c3412a3026Aa5F521Ea1e948E526", - EthereumBridgeConfigVersion: 0, - EthereumPrivateKey: "2aecee1800342bae06228ed990a152563b8dedf5fe15e3eab4b44854c9e001e5", + Port: 8000, + AdminPort: 6060, + PostgresDSN: "dbname=starbridge user=starbridge", + HorizonURL: "https://horizon-testnet.stellar.org", + NetworkPassphrase: "Test SDF Network ; September 2015", + StellarBridgeAccount: "GAJKCRY6CIOXRIVK55ALOOJA327XN4JZ5KKN7YCTT3WM5W6BMFXMVQC2", + StellarPrivateKey: "SCSTO3PMPM2BNLR2MYKVHWCJ2FNHQGFWKPOFH6UX4N3HO6HMK4JBSJ6F", + EthereumRPCURL: "https://ethereum-goerli-rpc.allthatnode.com", + EthereumBridgeAddress: "0xD0675839A6C2c3412a3026Aa5F521Ea1e948E526", + EthereumPrivateKey: "2aecee1800342bae06228ed990a152563b8dedf5fe15e3eab4b44854c9e001e5", AssetMapping: []backend.AssetMappingConfigEntry{ { StellarAsset: "ETH:GAJKCRY6CIOXRIVK55ALOOJA327XN4JZ5KKN7YCTT3WM5W6BMFXMVQC2", diff --git a/app/testdata/example.cfg b/app/testdata/example.cfg index 895384f..117dddb 100644 --- a/app/testdata/example.cfg +++ b/app/testdata/example.cfg @@ -7,7 +7,6 @@ stellar_bridge_account="GAJKCRY6CIOXRIVK55ALOOJA327XN4JZ5KKN7YCTT3WM5W6BMFXMVQC2 stellar_private_key="SCSTO3PMPM2BNLR2MYKVHWCJ2FNHQGFWKPOFH6UX4N3HO6HMK4JBSJ6F" ethereum_rpc_url="https://ethereum-goerli-rpc.allthatnode.com" ethereum_bridge_address="0xD0675839A6C2c3412a3026Aa5F521Ea1e948E526" -ethereum_bridge_config_version=0 ethereum_private_key="2aecee1800342bae06228ed990a152563b8dedf5fe15e3eab4b44854c9e001e5" [[asset_mapping]] diff --git a/ethereum/observer.go b/ethereum/observer.go index 8eb74a0..16cf85d 100644 --- a/ethereum/observer.go +++ b/ethereum/observer.go @@ -212,3 +212,9 @@ func (o Observer) GetRequestStatus(ctx context.Context, requestID common.Hash) ( BlockNumber: blockNumber.Uint64(), }, nil } + +// GetDomainSeparator calls the domainSeparator public attribute on the bridge contract +// and returns its value +func (o Observer) GetDomainSeparator(ctx context.Context) ([32]byte, error) { + return o.caller.DomainSeparator(&bind.CallOpts{Context: ctx}) +} diff --git a/ethereum/signer.go b/ethereum/signer.go index d0b09c6..1639aae 100644 --- a/ethereum/signer.go +++ b/ethereum/signer.go @@ -14,7 +14,6 @@ import ( ) var ( - uint256 = mustType("uint256") bytes32 = mustType("bytes32") withdrawERC20Type = mustTupleType([]abi.ArgumentMarshaling{ {Name: "id", Type: "bytes32"}, @@ -50,21 +49,21 @@ func mustTupleType(components []abi.ArgumentMarshaling) abi.Type { // Signer represents an ethereum validator account which is // authorized to approve withdrawals from the bridge smart contract. type Signer struct { - privateKey *ecdsa.PrivateKey - version *big.Int - address common.Address + privateKey *ecdsa.PrivateKey + domainSeparator [32]byte + address common.Address } // NewSigner constructs a new Signer instance -func NewSigner(privateKey string, bridgeConfigVersion uint32) (Signer, error) { +func NewSigner(privateKey string, domainSeparator [32]byte) (Signer, error) { parsed, err := crypto.HexToECDSA(privateKey) if err != nil { return Signer{}, err } return Signer{ - privateKey: parsed, - version: big.NewInt(int64(bridgeConfigVersion)), - address: crypto.PubkeyToAddress(parsed.PublicKey), + privateKey: parsed, + domainSeparator: domainSeparator, + address: crypto.PubkeyToAddress(parsed.PublicKey), }, nil } @@ -102,13 +101,13 @@ func (s Signer) SignWithdrawal( func (s Signer) signWithdrawERC20Request(request solidity.WithdrawERC20Request) ([]byte, error) { arguments := abi.Arguments{ - {Type: uint256}, + {Type: bytes32}, {Type: bytes32}, {Type: withdrawERC20Type}, } abiEncoded, err := arguments.Pack( - s.version, + s.domainSeparator, crypto.Keccak256Hash([]byte("withdrawERC20")), request, ) @@ -120,13 +119,13 @@ func (s Signer) signWithdrawERC20Request(request solidity.WithdrawERC20Request) func (s Signer) signWithdrawETHRequest(request solidity.WithdrawETHRequest) ([]byte, error) { arguments := abi.Arguments{ - {Type: uint256}, + {Type: bytes32}, {Type: bytes32}, {Type: withdrawETHType}, } abiEncoded, err := arguments.Pack( - s.version, + s.domainSeparator, crypto.Keccak256Hash([]byte("withdrawETH")), request, ) diff --git a/ethereum/signer_test.go b/ethereum/signer_test.go index 3834233..57a3c28 100644 --- a/ethereum/signer_test.go +++ b/ethereum/signer_test.go @@ -13,7 +13,7 @@ import ( func createSigner(t *testing.T) Signer { signer, err := NewSigner( "51138e68e8a5fa906d38c5b42bc01b805d7adb3fce037743fb406bb10aa83307", - 0, + [32]byte{1, 2, 3}, ) assert.NoError(t, err) return signer @@ -44,14 +44,14 @@ func TestSigner_SignWithdrawal(t *testing.T) { recipient: common.HexToAddress("0x123"), token: common.HexToAddress("0x456"), amount: big.NewInt(200), - expected: "2a4fead286732e459cc4f167aa34f6ca6b83fa4e7c993429582a048020a4c2840f47a9903257266605de9975d2122d1db8697b7398474889aafaa3c9d1b4bd6c1c", + expected: "d668c6d190f0a1dcb03b5540794479659a5d46cfa741d2e52f65b9d5e4afae420dae19570a2dd871486c886c0e19511eb1bb39299baa99baf7b73ef30190e0d91c", }, { id: common.HexToHash("0x55"), expiration: 200, recipient: common.HexToAddress("0x321"), amount: big.NewInt(100), - expected: "40cd596f0d1683bbe42c6fc57220ecfda15d78d5ce9ccdf68c4da4d9a4d1a6bf63d249d59b17ead7c59b4b4da9755aad6eb9eefe656685322de074128370d31e1c", + expected: "2f7c8c16b1cec4063b9df791ead00e4db539072963b07bec12957c8680dc1eee6d41b2e906746b9b619af2ffb5792d794c81a90381d8b26e40b913e5a0fe0f821b", }, } { signature, err := signer.SignWithdrawal( diff --git a/integration/integration.go b/integration/integration.go index 20b560c..09c1665 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -251,18 +251,17 @@ func (i *Test) StartStarbridge(id int, config Config, ingestSequence uint32) err i.signerKeys[id] = keypair.MustRandom() i.app[id] = app.NewApp(app.Config{ - Port: 9000 + uint16(id), - PostgresDSN: fmt.Sprintf("postgres://postgres:mysecretpassword@%s:5641/starbridge%d?sslmode=disable", dockerHost, id), - HorizonURL: fmt.Sprintf("http://%s:8000/", dockerHost), - NetworkPassphrase: StandaloneNetworkPassphrase, - StellarBridgeAccount: i.mainAccount.GetAccountID(), - StellarPrivateKey: i.signerKeys[id].Seed(), - EthereumRPCURL: EthereumRPCURL, - EthereumBridgeAddress: EthereumBridgeAddress, - EthereumBridgeConfigVersion: 0, - EthereumPrivateKey: ethPrivateKeys[id], - EthereumFinalityBuffer: 0, - WithdrawalWindow: config.WithdrawalWindow, + Port: 9000 + uint16(id), + PostgresDSN: fmt.Sprintf("postgres://postgres:mysecretpassword@%s:5641/starbridge%d?sslmode=disable", dockerHost, id), + HorizonURL: fmt.Sprintf("http://%s:8000/", dockerHost), + NetworkPassphrase: StandaloneNetworkPassphrase, + StellarBridgeAccount: i.mainAccount.GetAccountID(), + StellarPrivateKey: i.signerKeys[id].Seed(), + EthereumRPCURL: EthereumRPCURL, + EthereumBridgeAddress: EthereumBridgeAddress, + EthereumPrivateKey: ethPrivateKeys[id], + EthereumFinalityBuffer: 0, + WithdrawalWindow: config.WithdrawalWindow, AssetMapping: []backend.AssetMappingConfigEntry{ { StellarAsset: "native", From cd89110a8c10353b65fc00bbc12ca390f0a98b69 Mon Sep 17 00:00:00 2001 From: tamirms Date: Thu, 8 Sep 2022 17:11:30 +0200 Subject: [PATCH 4/7] add build step to integration test setup --- integration/integration.go | 1 + solidity/scripts/deploy.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/integration/integration.go b/integration/integration.go index 09c1665..4f07aa5 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -107,6 +107,7 @@ func NewIntegrationTest(t *testing.T, config Config) *Test { } test.runComposeCommand("down", "-v") + test.runComposeCommand("build") test.runComposeCommand("up", "--detach", "--quiet-pull", "--no-color", "starbridge-postgres") test.runComposeCommand("up", "--detach", "--quiet-pull", "--no-color", "quickstart") test.runComposeCommand("up", "--detach", "--no-color", "ethereum-node") diff --git a/solidity/scripts/deploy.js b/solidity/scripts/deploy.js index 0d74ac3..2a3c82b 100644 --- a/solidity/scripts/deploy.js +++ b/solidity/scripts/deploy.js @@ -36,6 +36,8 @@ async function main() { const wrappedXLM = await getToken(bridge, await registerStellarAsset(bridge, signers, 0, "Stellar Lumens", "XLM", 7)); console.log("Wrapped XLM address:", wrappedXLM.address); + + console.log("domain separator: ", await bridge.domainSeparator()); } main() From 978ba8cc7c739514fea3854e59551863137055fb Mon Sep 17 00:00:00 2001 From: tamirms Date: Thu, 8 Sep 2022 17:50:35 +0200 Subject: [PATCH 5/7] fix deploy script --- solidity/scripts/deploy.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/solidity/scripts/deploy.js b/solidity/scripts/deploy.js index 2a3c82b..c0914f0 100644 --- a/solidity/scripts/deploy.js +++ b/solidity/scripts/deploy.js @@ -1,11 +1,11 @@ const { ethers } = require("hardhat"); -async function registerStellarAsset(bridge, signers, configVersion, name, symbol, decimals) { +async function registerStellarAsset(bridge, signers, domainSeparator, name, symbol, decimals) { const request = [decimals, name, symbol]; const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "uint8", "bytes32", "bytes32"], + ["bytes32", "bytes32", "uint8", "bytes32", "bytes32"], [ - configVersion, + domainSeparator, ethers.utils.id("registerStellarAsset"), decimals, ethers.utils.id(name), @@ -34,10 +34,11 @@ async function main() { const bridge = await Bridge.deploy(addresses, 2); console.log("Bridge address:", bridge.address); - const wrappedXLM = await getToken(bridge, await registerStellarAsset(bridge, signers, 0, "Stellar Lumens", "XLM", 7)); - console.log("Wrapped XLM address:", wrappedXLM.address); + const domainSeparator = await bridge.domainSeparator(); + console.log("domain separator: ", domainSeparator); - console.log("domain separator: ", await bridge.domainSeparator()); + const wrappedXLM = await getToken(bridge, await registerStellarAsset(bridge, signers, domainSeparator, "Stellar Lumens", "XLM", 7)); + console.log("Wrapped XLM address:", wrappedXLM.address); } main() From 559b8a9cac163ea9487383a2c6ddf1a02d6e9512 Mon Sep 17 00:00:00 2001 From: tamirms Date: Fri, 9 Sep 2022 08:33:38 +0200 Subject: [PATCH 6/7] explicitly check amount of tokens deposited to bridge --- solidity/contracts/Bridge.sol | 12 +++++++++--- solidity/contracts/FeeToken.sol | 22 ++++++++++++++++++++++ solidity/test/erc20.js | 14 ++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 solidity/contracts/FeeToken.sol diff --git a/solidity/contracts/Bridge.sol b/solidity/contracts/Bridge.sol index f1f25cd..f4563b8 100644 --- a/solidity/contracts/Bridge.sol +++ b/solidity/contracts/Bridge.sol @@ -3,8 +3,10 @@ pragma solidity ^0.8.0; import "./Auth.sol"; import "./StellarAsset.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/math/SafeMath.sol"; // Every bridge transfer has a globally unique id. // @@ -80,7 +82,7 @@ bytes32 constant WITHDRAW_ETH_ID = keccak256("withdrawETH"); // WITHDRAW_ERC20_ID is used to distinguish withdrawERC20() signatures from signatures for other bridge functions. bytes32 constant WITHDRAW_ERC20_ID = keccak256("withdrawERC20"); -contract Bridge is Auth { +contract Bridge is Auth, ReentrancyGuard { // paused is a bitmask which determines whether deposits / withdrawals are enabled on the bridge uint8 public paused; // SetPaused is emitted whenever the paused state of the bridge changes @@ -134,7 +136,7 @@ contract Bridge is Auth { address token, uint256 destination, uint256 amount - ) external { + ) external nonReentrant { require((paused & PAUSE_DEPOSITS) == 0, "deposits are paused"); require(amount > 0, "deposit amount is zero"); require(token != address(0x0), "invalid token address"); @@ -145,12 +147,16 @@ contract Bridge is Auth { if (isStellarAsset[token]) { StellarAsset(token).burn(msg.sender, amount); } else { + IERC20 tokenContract = IERC20(token); + uint256 before = tokenContract.balanceOf(address(this)); SafeERC20.safeTransferFrom( - IERC20(token), + tokenContract, msg.sender, address(this), amount ); + uint256 received = SafeMath.sub(tokenContract.balanceOf(address(this)), before); + require(amount == received, "received amount not equal to expected amount"); } } diff --git a/solidity/contracts/FeeToken.sol b/solidity/contracts/FeeToken.sol new file mode 100644 index 0000000..f937984 --- /dev/null +++ b/solidity/contracts/FeeToken.sol @@ -0,0 +1,22 @@ +// contracts/Auth.sol +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; +import "./StellarAsset.sol"; + + +contract FeeToken is StellarAsset { + + constructor( + string memory name_, + string memory symbol_, + uint8 decimals_ + ) StellarAsset(name_, symbol_, decimals_) {} + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) public virtual override returns (bool) { + return super.transferFrom(sender, recipient, amount-1); + } +} \ No newline at end of file diff --git a/solidity/test/erc20.js b/solidity/test/erc20.js index 83ed1f4..e537117 100644 --- a/solidity/test/erc20.js +++ b/solidity/test/erc20.js @@ -54,6 +54,20 @@ describe("Deposit & Withdraw ERC20", function() { await setPaused(bridge, signers, domainSeparator, PAUSE_NOTHING, nextPauseNonce(), validTimestamp()); }); + it("block deposits for tokens which transfer less than expected amount", async function() { + const FeeToken = await ethers.getContractFactory("FeeToken"); + const feeToken = await FeeToken.deploy("Fee Token", "FEE", 18); + await feeToken.mint(sender.address, ethers.utils.parseEther("100.0")); + await feeToken.approve(bridge.address, ethers.utils.parseEther("300.0")); + + await setDepositAllowed(bridge, signers, domainSeparator, feeToken.address, true, 1, validTimestamp()); + expect(await bridge.depositAllowed(feeToken.address)).to.be.true; + + await expect(bridge.depositERC20( + feeToken.address, 1, ethers.utils.parseEther("1.0") + )).to.be.revertedWith("received amount not equal to expected amount"); + }); + it("block deposits for a specific ERC20 token", async function() { const blockedToken = await ERC20.deploy("Blocked Test Token", "BLOCKED", 18); await blockedToken.mint(sender.address, ethers.utils.parseEther("100.0")); From 1bf9065a57cd542fb985dee7c88fe54fff6a6318 Mon Sep 17 00:00:00 2001 From: tamirms Date: Fri, 9 Sep 2022 09:00:57 +0200 Subject: [PATCH 7/7] remove deposit token allow list --- solidity-go/bridge.go | 197 +------------------------- solidity/contracts/Bridge.sol | 47 +----- solidity/test/bridge.js | 1 - solidity/test/erc20.js | 35 ----- solidity/test/eth.js | 29 ---- solidity/test/registerStellarAsset.js | 1 - solidity/test/setDepositAllowed.js | 153 -------------------- 7 files changed, 2 insertions(+), 461 deletions(-) delete mode 100644 solidity/test/setDepositAllowed.js diff --git a/solidity-go/bridge.go b/solidity-go/bridge.go index 742a475..fbcb65a 100644 --- a/solidity-go/bridge.go +++ b/solidity-go/bridge.go @@ -35,14 +35,6 @@ type RegisterStellarAssetRequest struct { Symbol string } -// SetDepositAllowedRequest is an auto generated low-level Go binding around an user-defined struct. -type SetDepositAllowedRequest struct { - Token common.Address - Allowed bool - Nonce *big.Int - Expiration *big.Int -} - // SetPausedRequest is an auto generated low-level Go binding around an user-defined struct. type SetPausedRequest struct { Value uint8 @@ -69,7 +61,7 @@ type WithdrawETHRequest struct { // BridgeMetaData contains all meta data concerning the Bridge contract. var BridgeMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_minThreshold\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"domainSeparator\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"minThreshold\",\"type\":\"uint8\"}],\"name\":\"RegisterSigners\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"RegisterStellarAsset\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"}],\"name\":\"SetDepositAllowed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"value\",\"type\":\"uint8\"}],\"name\":\"SetPaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"depositAllowed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"depositERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"}],\"name\":\"depositETH\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"domainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isStellarAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minThreshold\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"symbol\",\"type\":\"string\"}],\"internalType\":\"structRegisterStellarAssetRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"registerStellarAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestID\",\"type\":\"bytes32\"}],\"name\":\"requestStatus\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"internalType\":\"structSetDepositAllowedRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"setDepositAllowed\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"value\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"internalType\":\"structSetPausedRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"setPaused\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"signers\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_minThreshold\",\"type\":\"uint8\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"updateSigners\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structWithdrawERC20Request\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"withdrawERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structWithdrawETHRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"withdrawETH\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_minThreshold\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"domainSeparator\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"minThreshold\",\"type\":\"uint8\"}],\"name\":\"RegisterSigners\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"}],\"name\":\"RegisterStellarAsset\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"value\",\"type\":\"uint8\"}],\"name\":\"SetPaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"depositERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"}],\"name\":\"depositETH\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"domainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isStellarAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minThreshold\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"decimals\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"symbol\",\"type\":\"string\"}],\"internalType\":\"structRegisterStellarAssetRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"registerStellarAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestID\",\"type\":\"bytes32\"}],\"name\":\"requestStatus\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"value\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"internalType\":\"structSetPausedRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"setPaused\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"signers\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_minThreshold\",\"type\":\"uint8\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"updateSigners\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structWithdrawERC20Request\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"withdrawERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structWithdrawETHRequest\",\"name\":\"request\",\"type\":\"tuple\"},{\"internalType\":\"bytes[]\",\"name\":\"signatures\",\"type\":\"bytes[]\"},{\"internalType\":\"uint8[]\",\"name\":\"indexes\",\"type\":\"uint8[]\"}],\"name\":\"withdrawETH\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", } // BridgeABI is the input ABI used to generate the binding from. @@ -218,37 +210,6 @@ func (_Bridge *BridgeTransactorRaw) Transact(opts *bind.TransactOpts, method str return _Bridge.Contract.contract.Transact(opts, method, params...) } -// DepositAllowed is a free data retrieval call binding the contract method 0xbd9f9b47. -// -// Solidity: function depositAllowed(address ) view returns(bool) -func (_Bridge *BridgeCaller) DepositAllowed(opts *bind.CallOpts, arg0 common.Address) (bool, error) { - var out []interface{} - err := _Bridge.contract.Call(opts, &out, "depositAllowed", arg0) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// DepositAllowed is a free data retrieval call binding the contract method 0xbd9f9b47. -// -// Solidity: function depositAllowed(address ) view returns(bool) -func (_Bridge *BridgeSession) DepositAllowed(arg0 common.Address) (bool, error) { - return _Bridge.Contract.DepositAllowed(&_Bridge.CallOpts, arg0) -} - -// DepositAllowed is a free data retrieval call binding the contract method 0xbd9f9b47. -// -// Solidity: function depositAllowed(address ) view returns(bool) -func (_Bridge *BridgeCallerSession) DepositAllowed(arg0 common.Address) (bool, error) { - return _Bridge.Contract.DepositAllowed(&_Bridge.CallOpts, arg0) -} - // DomainSeparator is a free data retrieval call binding the contract method 0xf698da25. // // Solidity: function domainSeparator() view returns(bytes32) @@ -530,27 +491,6 @@ func (_Bridge *BridgeTransactorSession) RegisterStellarAsset(request RegisterSte return _Bridge.Contract.RegisterStellarAsset(&_Bridge.TransactOpts, request, signatures, indexes) } -// SetDepositAllowed is a paid mutator transaction binding the contract method 0x73899162. -// -// Solidity: function setDepositAllowed((address,bool,uint256,uint256) request, bytes[] signatures, uint8[] indexes) returns() -func (_Bridge *BridgeTransactor) SetDepositAllowed(opts *bind.TransactOpts, request SetDepositAllowedRequest, signatures [][]byte, indexes []uint8) (*types.Transaction, error) { - return _Bridge.contract.Transact(opts, "setDepositAllowed", request, signatures, indexes) -} - -// SetDepositAllowed is a paid mutator transaction binding the contract method 0x73899162. -// -// Solidity: function setDepositAllowed((address,bool,uint256,uint256) request, bytes[] signatures, uint8[] indexes) returns() -func (_Bridge *BridgeSession) SetDepositAllowed(request SetDepositAllowedRequest, signatures [][]byte, indexes []uint8) (*types.Transaction, error) { - return _Bridge.Contract.SetDepositAllowed(&_Bridge.TransactOpts, request, signatures, indexes) -} - -// SetDepositAllowed is a paid mutator transaction binding the contract method 0x73899162. -// -// Solidity: function setDepositAllowed((address,bool,uint256,uint256) request, bytes[] signatures, uint8[] indexes) returns() -func (_Bridge *BridgeTransactorSession) SetDepositAllowed(request SetDepositAllowedRequest, signatures [][]byte, indexes []uint8) (*types.Transaction, error) { - return _Bridge.Contract.SetDepositAllowed(&_Bridge.TransactOpts, request, signatures, indexes) -} - // SetPaused is a paid mutator transaction binding the contract method 0xfac7d40f. // // Solidity: function setPaused((uint8,uint256,uint256) request, bytes[] signatures, uint8[] indexes) returns() @@ -1043,141 +983,6 @@ func (_Bridge *BridgeFilterer) ParseRegisterStellarAsset(log types.Log) (*Bridge return event, nil } -// BridgeSetDepositAllowedIterator is returned from FilterSetDepositAllowed and is used to iterate over the raw logs and unpacked data for SetDepositAllowed events raised by the Bridge contract. -type BridgeSetDepositAllowedIterator struct { - Event *BridgeSetDepositAllowed // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *BridgeSetDepositAllowedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(BridgeSetDepositAllowed) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(BridgeSetDepositAllowed) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *BridgeSetDepositAllowedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *BridgeSetDepositAllowedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// BridgeSetDepositAllowed represents a SetDepositAllowed event raised by the Bridge contract. -type BridgeSetDepositAllowed struct { - Token common.Address - Allowed bool - Raw types.Log // Blockchain specific contextual infos -} - -// FilterSetDepositAllowed is a free log retrieval operation binding the contract event 0x4db541bbea3efdaa1d5156f38b6874e3952f2173563fe2975acbcee62cee9ca0. -// -// Solidity: event SetDepositAllowed(address token, bool allowed) -func (_Bridge *BridgeFilterer) FilterSetDepositAllowed(opts *bind.FilterOpts) (*BridgeSetDepositAllowedIterator, error) { - - logs, sub, err := _Bridge.contract.FilterLogs(opts, "SetDepositAllowed") - if err != nil { - return nil, err - } - return &BridgeSetDepositAllowedIterator{contract: _Bridge.contract, event: "SetDepositAllowed", logs: logs, sub: sub}, nil -} - -// WatchSetDepositAllowed is a free log subscription operation binding the contract event 0x4db541bbea3efdaa1d5156f38b6874e3952f2173563fe2975acbcee62cee9ca0. -// -// Solidity: event SetDepositAllowed(address token, bool allowed) -func (_Bridge *BridgeFilterer) WatchSetDepositAllowed(opts *bind.WatchOpts, sink chan<- *BridgeSetDepositAllowed) (event.Subscription, error) { - - logs, sub, err := _Bridge.contract.WatchLogs(opts, "SetDepositAllowed") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(BridgeSetDepositAllowed) - if err := _Bridge.contract.UnpackLog(event, "SetDepositAllowed", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseSetDepositAllowed is a log parse operation binding the contract event 0x4db541bbea3efdaa1d5156f38b6874e3952f2173563fe2975acbcee62cee9ca0. -// -// Solidity: event SetDepositAllowed(address token, bool allowed) -func (_Bridge *BridgeFilterer) ParseSetDepositAllowed(log types.Log) (*BridgeSetDepositAllowed, error) { - event := new(BridgeSetDepositAllowed) - if err := _Bridge.contract.UnpackLog(event, "SetDepositAllowed", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - // BridgeSetPausedIterator is returned from FilterSetPaused and is used to iterate over the raw logs and unpacked data for SetPaused events raised by the Bridge contract. type BridgeSetPausedIterator struct { Event *BridgeSetPaused // Event containing the contract specifics and raw log diff --git a/solidity/contracts/Bridge.sol b/solidity/contracts/Bridge.sol index f4563b8..c9b700e 100644 --- a/solidity/contracts/Bridge.sol +++ b/solidity/contracts/Bridge.sol @@ -47,14 +47,6 @@ struct SetPausedRequest { uint256 expiration; // unix timestamp of when the transaction should expire } -// SetDepositAllowedRequest is the payload for the setDepositAllowed() transaction. -struct SetDepositAllowedRequest { - address token; // token to be enabled / disabled - bool allowed; // true if depsits are allowed otherwise false - uint256 nonce; // used to make each transaction unique for replay prevention - uint256 expiration; // unix timestamp of when the transaction should expire -} - // RegisterStellarAssetRequest is the payload for the registerStellarAsset() transaction. // The three fields define a new ERC20 token which represents the ethereum equivalent of // a Stellar asset. @@ -71,8 +63,6 @@ uint8 constant PAUSE_WITHDRAWALS = 1 << 1; // bitwise flag representing the state where no withdrawals or deposits are allowed on the bridge uint8 constant PAUSE_DEPOSITS_AND_WITHDRAWALS = PAUSE_DEPOSITS | PAUSE_WITHDRAWALS; -// SET_DEPOSIT_ALLOWED is used to distinguish setDepositAllowed() signatures from signatures for other bridge functions. -bytes32 constant SET_DEPOSIT_ALLOWED = keccak256("setDepositAllowed"); // SET_PAUSED_ID is used to distinguish setPaused() signatures from signatures for other bridge functions. bytes32 constant SET_PAUSED_ID = keccak256("setPaused"); // REGISTER_STELLAR_ASSET_ID is used to distinguish registerStellarAsset() signatures from signatures for other bridge functions. @@ -89,10 +79,7 @@ contract Bridge is Auth, ReentrancyGuard { event SetPaused(uint8 value); // to create a Bridge instance you need to provide the validator set configuration - constructor(address[] memory _signers, uint8 _minThreshold) Auth(_signers, _minThreshold) { - emit SetDepositAllowed(address(0x0), true); - depositAllowed[address(0x0)] = true; - } + constructor(address[] memory _signers, uint8 _minThreshold) Auth(_signers, _minThreshold) {} // Deposit is emitted whenever ERC20 tokens (or ETH) are deposited on the bridge. // The Deposit event initiates a Ethereum -> Stellar transfer. @@ -123,13 +110,6 @@ contract Bridge is Auth, ReentrancyGuard { // created by the bridge. mapping(address => bool) public isStellarAsset; - // depositAllowed identifies whether an ERC20 token is can be deposited on - // the bridge. - mapping(address => bool) public depositAllowed; - - // SetPaused is emitted whenever the paused state of the bridge changes - event SetDepositAllowed(address token, bool allowed); - // depositERC20() deposits ERC20 tokens to the bridge and starts a ERC20 -> Stellar // transfer. If deposits are disabled this function will fail. function depositERC20( @@ -139,8 +119,6 @@ contract Bridge is Auth, ReentrancyGuard { ) external nonReentrant { require((paused & PAUSE_DEPOSITS) == 0, "deposits are paused"); require(amount > 0, "deposit amount is zero"); - require(token != address(0x0), "invalid token address"); - require(depositAllowed[token], "deposits not allowed for token"); emit Deposit(token, msg.sender, destination, amount); @@ -163,7 +141,6 @@ contract Bridge is Auth, ReentrancyGuard { // depositETH() deposits ETH to the bridge and starts a ETH -> Stellar // transfer. If deposits are disabled this function will fail. function depositETH(uint256 destination) external payable { - require(depositAllowed[address(0)], "eth deposits are not allowed"); require((paused & PAUSE_DEPOSITS) == 0, "deposits are paused"); require(msg.value > 0, "deposit amount is zero"); emit Deposit(address(0), msg.sender, destination, msg.value); @@ -247,26 +224,6 @@ contract Bridge is Auth, ReentrancyGuard { paused = request.value; } - // setDepositAllowed() will enable or disable deposits for a specific token. - // setDepositAllowed() must be authorized by the bridge validators otherwise the transaction - // will fail. Replay prevention is implemented by storing the request hash in the - // fulfilledrequests set. - function setDepositAllowed( - SetDepositAllowedRequest memory request, - bytes[] calldata signatures, - uint8[] calldata indexes - ) external { - bytes32 requestHash = keccak256(abi.encode(domainSeparator, SET_DEPOSIT_ALLOWED, request)); - // ensure the same setPaused() transaction cannot be used more than once - verifyRequest(requestHash, requestHash, request.expiration, signatures, indexes); - emit SetDepositAllowed(request.token, request.allowed); - if (request.allowed) { - depositAllowed[request.token] = true; - } else { - delete(depositAllowed[request.token]); - } - } - // registerStellarAsset() will creates an ERC20 token to represent a stellar asset. // registerStellarAsset() must be authorized by the bridge validators otherwise the transaction // will fail. Replay prevention is impemented by creating the ERC20 via the CREATE2 opcode (see @@ -303,8 +260,6 @@ contract Bridge is Auth, ReentrancyGuard { ); emit RegisterStellarAsset(asset); - emit SetDepositAllowed(asset, true); isStellarAsset[asset] = true; - depositAllowed[asset] = true; } } diff --git a/solidity/test/bridge.js b/solidity/test/bridge.js index 5291056..378f516 100644 --- a/solidity/test/bridge.js +++ b/solidity/test/bridge.js @@ -13,7 +13,6 @@ describe("Deploy Bridge", function() { for(let i = 0; i < 20; i++) { expect(await bridge.signers(i)).to.equal(addresses[i]); } - expect(await bridge.depositAllowed("0x0000000000000000000000000000000000000000")).to.be.true; await expect(bridge.signers(20)).to.be.reverted; }); diff --git a/solidity/test/erc20.js b/solidity/test/erc20.js index e537117..abb9232 100644 --- a/solidity/test/erc20.js +++ b/solidity/test/erc20.js @@ -3,7 +3,6 @@ const { ethers } = require("hardhat"); const { PAUSE_DEPOSITS, PAUSE_NOTHING, PAUSE_WITHDRAWALS_AND_DEPOSITS, setPaused, nextPauseNonce, PAUSE_WITHDRAWALS } = require("./paused"); const { updateSigners } = require("./updateSigners"); const { validTimestamp, expiredTimestamp } = require("./util"); -const { setDepositAllowed } = require("./setDepositAllowed"); async function withdrawERC20(bridge, token, signers, id, domainSeparator, expiration, recipient, amount) { const request = [id, expiration, recipient, token.address, amount]; @@ -37,7 +36,6 @@ describe("Deposit & Withdraw ERC20", function() { token = await ERC20.deploy("Test Token", "TEST", 18); await token.mint(sender.address, ethers.utils.parseEther("100.0")); await token.approve(bridge.address, ethers.utils.parseEther("300.0")); - await setDepositAllowed(bridge, signers, domainSeparator, token.address, true, 0, validTimestamp()); }); it("deposits of 0 are rejected", async function() { @@ -60,44 +58,11 @@ describe("Deposit & Withdraw ERC20", function() { await feeToken.mint(sender.address, ethers.utils.parseEther("100.0")); await feeToken.approve(bridge.address, ethers.utils.parseEther("300.0")); - await setDepositAllowed(bridge, signers, domainSeparator, feeToken.address, true, 1, validTimestamp()); - expect(await bridge.depositAllowed(feeToken.address)).to.be.true; - await expect(bridge.depositERC20( feeToken.address, 1, ethers.utils.parseEther("1.0") )).to.be.revertedWith("received amount not equal to expected amount"); }); - it("block deposits for a specific ERC20 token", async function() { - const blockedToken = await ERC20.deploy("Blocked Test Token", "BLOCKED", 18); - await blockedToken.mint(sender.address, ethers.utils.parseEther("100.0")); - await blockedToken.approve(bridge.address, ethers.utils.parseEther("300.0")); - - expect(await bridge.depositAllowed(blockedToken.address)).to.be.false; - await expect(bridge.depositERC20( - blockedToken.address, 1, ethers.utils.parseEther("1.0") - )).to.be.revertedWith("deposits not allowed for token"); - - await setDepositAllowed(bridge, signers, domainSeparator, blockedToken.address, true, 1, validTimestamp()); - expect(await bridge.depositAllowed(blockedToken.address)).to.be.true; - - const before = await token.balanceOf(bridge.address); - - await bridge.depositERC20( - blockedToken.address, 1, ethers.utils.parseEther("1.0") - ); - - const after = await blockedToken.balanceOf(bridge.address); - expect(after.sub(before)).to.equal(ethers.utils.parseEther("1.0")); - - await setDepositAllowed(bridge, signers, domainSeparator, blockedToken.address, false, 2, validTimestamp()); - - expect(await bridge.depositAllowed(blockedToken.address)).to.be.false; - await expect(bridge.depositERC20( - blockedToken.address, 1, ethers.utils.parseEther("1.0") - )).to.be.revertedWith("deposits not allowed for token"); - }); - it("cannot deposit more tokens than current balance", async function() { await expect(bridge.depositERC20( token.address, 1, ethers.utils.parseEther("200") diff --git a/solidity/test/eth.js b/solidity/test/eth.js index 511fbd0..a766a87 100644 --- a/solidity/test/eth.js +++ b/solidity/test/eth.js @@ -3,7 +3,6 @@ const { ethers, waffle } = require("hardhat"); const { PAUSE_DEPOSITS, PAUSE_NOTHING, PAUSE_WITHDRAWALS_AND_DEPOSITS, setPaused, nextPauseNonce, PAUSE_WITHDRAWALS } = require("./paused"); const { updateSigners } = require("./updateSigners"); const { validTimestamp, expiredTimestamp } = require("./util"); -const { setDepositAllowed } = require("./setDepositAllowed"); describe("Deposit & Withdraw ETH", function() { let signers; @@ -31,34 +30,6 @@ describe("Deposit & Withdraw ETH", function() { await expect(bridge.depositETH(1, {value: 0})).to.be.revertedWith("deposit amount is zero"); }); - it("block deposits of ETH", async function() { - const ERC20 = await ethers.getContractFactory("StellarAsset"); - const token = await ERC20.deploy("Blocked Test Token", "BLOCKED", 18); - await token.mint(sender.address, ethers.utils.parseEther("100.0")); - await token.approve(bridge.address, ethers.utils.parseEther("300.0")); - - await setDepositAllowed(bridge, signers, domainSeparator, token.address, true, 0, validTimestamp()); - expect(await bridge.depositAllowed(token.address)).to.be.true; - - const ethAddress = "0x0000000000000000000000000000000000000000"; - await setDepositAllowed(bridge, signers, domainSeparator, ethAddress, false, 1, validTimestamp()); - expect(await bridge.depositAllowed(ethAddress)).to.be.false; - - const before = await token.balanceOf(bridge.address); - - await bridge.depositERC20( - token.address, 1, ethers.utils.parseEther("1.0") - ); - - const after = await token.balanceOf(bridge.address); - expect(after.sub(before)).to.equal(ethers.utils.parseEther("1.0")); - - await expect(bridge.depositETH(1, {value: ethers.utils.parseEther("1.0")})).to.be.revertedWith("eth deposits are not allowed"); - - await setDepositAllowed(bridge, signers, domainSeparator, ethAddress, true, 2, validTimestamp()); - expect(await bridge.depositAllowed(ethAddress)).to.be.true; - }); - it("deposits are rejected when bridge is paused", async function() { await setPaused(bridge, signers, domainSeparator, PAUSE_DEPOSITS, nextPauseNonce(), validTimestamp()); await expect(bridge.depositETH(1, {value: ethers.utils.parseEther("1.0")})).to.be.revertedWith("deposits are paused"); diff --git a/solidity/test/registerStellarAsset.js b/solidity/test/registerStellarAsset.js index 0ce229a..f9f145f 100644 --- a/solidity/test/registerStellarAsset.js +++ b/solidity/test/registerStellarAsset.js @@ -49,7 +49,6 @@ describe("registerStellarAsset", function() { expect(await wrappedXLM.name()).to.be.eql("Stellar Lumens"); expect(await wrappedXLM.symbol()).to.be.eql("XLM"); expect(await bridge.isStellarAsset(wrappedXLM.address)).to.be.true; - expect(await bridge.depositAllowed(wrappedXLM.address)).to.be.true; }); it("rejects duplicate transactions", async function() { diff --git a/solidity/test/setDepositAllowed.js b/solidity/test/setDepositAllowed.js deleted file mode 100644 index 85232a4..0000000 --- a/solidity/test/setDepositAllowed.js +++ /dev/null @@ -1,153 +0,0 @@ -const { expect } = require("chai"); -const { ethers } = require("hardhat"); -const { expiredTimestamp, validTimestamp } = require("./util"); -const { updateSigners } = require("./updateSigners"); - -async function setDepositAllowed(bridge, signers, domainSeparator, token, allowed, nonce, expiration) { - const request = [token, allowed, nonce, expiration]; - const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [domainSeparator, ethers.utils.id("setDepositAllowed"), request] - ))); - const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); - return bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]); -} - -describe("setDepositAllowed", function() { - let signers; - let bridge; - let domainSeparator; - const ethAddress = "0x0000000000000000000000000000000000000000"; - - this.beforeAll(async function() { - signers = (await ethers.getSigners()).slice(0, 20); - signers.sort((a, b) => a.address.toLowerCase().localeCompare(b.address.toLowerCase())); - const addresses = signers.map(a => a.address); - - const Bridge = await ethers.getContractFactory("Bridge"); - bridge = await Bridge.deploy(addresses, 20); - domainSeparator = await bridge.domainSeparator(); - }); - - it("is rejected if expired", async function() { - await expect( - setDepositAllowed(bridge, signers, domainSeparator, ethAddress, false, 0, expiredTimestamp()) - ).to.be.revertedWith("request is expired"); - }); - - it("is rejected if method id is invalid", async function() { - const request = [ethAddress, false, 0, validTimestamp()]; - const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [domainSeparator, ethers.utils.id("setDepositAllowed1"), request] - ))); - const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); - await expect( - bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]) - ).to.be.revertedWith("signature does not match"); - }); - - it("is rejected if domain separator is invalid", async function() { - const invalidDomainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint256", "address"], - [(await bridge.version())+1, 31337, bridge.address] - )); - const request = [ethAddress, false, 0, validTimestamp()]; - const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [invalidDomainSeparator, ethers.utils.id("setDepositAllowed"), request] - ))); - const signatures = await Promise.all(signers.map(s => s.signMessage(hash))); - await expect( - bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]) - ).to.be.revertedWith("signature does not match"); - }); - - it("is rejected if there are too few signatures", async function() { - const request = [ethAddress, false, 0, validTimestamp()]; - const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [domainSeparator, ethers.utils.id("setDepositAllowed"), request] - ))); - const signatures = await Promise.all(signers.slice(0,19).map(s => s.signMessage(hash))); - await expect( - bridge.setDepositAllowed(request, signatures, [...Array(signatures.length).keys()]) - ).to.be.revertedWith("not enough signatures"); - }); - - it("is rejected if there are invalid signatures", async function() { - const request = [ethAddress, false, 0, validTimestamp()]; - const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [domainSeparator, ethers.utils.id("setDepositAllowed"), request] - ))); - const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); - signatures[0] = signatures[1]; - await expect( - bridge.setDepositAllowed(request, signatures, [...Array(signatures.length).keys()]) - ).to.be.revertedWith("signature does not match"); - }); - - it("is rejected if the signatures are not sorted", async function() { - const request = [ethAddress, false, 0, validTimestamp()]; - const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [domainSeparator, ethers.utils.id("setDepositAllowed"), request] - ))); - const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); - const tmp = signatures[1]; - signatures[1] = signatures[0]; - signatures[0] = tmp; - const indexes = [...Array(20).keys()]; - indexes[0] = 1; - indexes[1] = 0; - await expect( - bridge.setDepositAllowed(request, signatures, indexes) - ).to.be.revertedWith("signatures not sorted by signer"); - }); - - it("is rejected if the indexes length does not match signatures length", async function() { - const request = [ethAddress, false, 0, validTimestamp()]; - const hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [domainSeparator, ethers.utils.id("setDepositAllowed"), request] - ))); - const signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); - await expect( - bridge.setDepositAllowed(request, signatures, [...Array(19).keys()]) - ).to.be.revertedWith("number of signatures does not equal number of indexes"); - }); - - it("nonce prevents transaction replay", async function() { - const request = [ethAddress, false, 0, validTimestamp()]; - let hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [domainSeparator, ethers.utils.id("setDepositAllowed"), request] - ))); - let signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); - await bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]); - - // reusing transaction will be rejected - await expect( - bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]) - ).to.be.revertedWith("request is already fulfilled"); - }); - - it("updateSigners invalidates setDepositAllowed transactions", async function() { - await updateSigners(bridge, signers, domainSeparator, signers.map(s => s.address), signers.length); - - const request = [ethAddress, true, 1, validTimestamp()]; - let hash = ethers.utils.arrayify(ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32", "tuple(address, bool, uint256, uint256)"], - [domainSeparator, ethers.utils.id("setDepositAllowed"), request] - ))); - let signatures = await Promise.all(signers.slice(0,20).map(s => s.signMessage(hash))); - await expect( - bridge.setDepositAllowed(request, signatures, [...Array(20).keys()]) - ).to.be.revertedWith("signature does not match"); - }); -}); - -module.exports = { - setDepositAllowed, -}; \ No newline at end of file