diff --git a/contracts/example/Colors.sol b/contracts/example/Colors.sol new file mode 100644 index 00000000..13f5ae7e --- /dev/null +++ b/contracts/example/Colors.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.11; + +contract Colors { + bytes32[] public colorsArray; + + uint256 public colorCounter = 0; + + event setColorEvent(bytes32 color); + event metadataDepositorEvent(address depositorAddress); + + function setColorOnDeploy(bytes32 color) public { + colorsArray.push(color); + } + + function setColor(bytes32 metadataDepositor, bytes32 color) public { + colorsArray.push(color); + colorCounter++; + + address depositorAddress = address(uint160(uint256(metadataDepositor))); + + emit setColorEvent(color); + emit metadataDepositorEvent(depositorAddress); + } + + function popColor() public { + colorsArray.pop(); + } + + function getColorsArrayLenght() public view returns (uint256 l) { + return colorsArray.length; + } + + function getCurrentColors(uint256 index) + public + view + returns (bytes32 colorReturned) + { + colorReturned = colorsArray[index]; + return colorReturned; + } + + function insertColorToColorsArray(bytes32 newColor) public { + colorsArray.push(newColor); + emit setColorEvent(newColor); + } + + function findColor(bytes32 color) + public + view + returns (bytes32 colorFound) + { + for (uint i = 0; i < colorsArray.length; i++) { + bytes32 c = colorsArray[i]; + + if ( + keccak256(abi.encodePacked((color))) == + keccak256(abi.encodePacked((c))) + ) { + colorFound = c; + break; + } + } + return colorFound; + } +} diff --git a/contracts/handlers/GenericHandlerV1.sol b/contracts/handlers/GenericHandlerV1.sol index faff43d4..76c0dbf5 100644 --- a/contracts/handlers/GenericHandlerV1.sol +++ b/contracts/handlers/GenericHandlerV1.sol @@ -58,7 +58,7 @@ contract GenericHandlerV1 is IGenericHandler { maxFee: uint256 bytes 96 - 128 metaData: metadataDepositor: address padded to 32 bytes bytes 128 - 160 - executionData: bytes bytes 128 - len(metaData) + executionData: bytes bytes 160 - (128 + len(metaData)) */ function deposit(bytes32 resourceID, address depositor, bytes calldata data) external returns (bytes memory) { require(data.length > 160, "Incorrect data length"); @@ -88,7 +88,7 @@ contract GenericHandlerV1 is IGenericHandler { maxFee: uint256 bytes 96 - 128 metadata: metadataDepositor: address padded to 32 bytes bytes 128 - 160 - executionData: bytes bytes 128 - len(metaData) + executionData: bytes bytes 160 - (128 + len(metaData)) */ function executeProposal(bytes32 resourceID, bytes calldata data) external { uint256 lenMetadata; diff --git a/migrations/3_deploy_genericHandlerV1.js b/migrations/3_deploy_genericHandlerV1.js index 070af28b..679d2f7c 100644 --- a/migrations/3_deploy_genericHandlerV1.js +++ b/migrations/3_deploy_genericHandlerV1.js @@ -22,7 +22,6 @@ module.exports = async function(deployer, network) { // fetch deployed contracts addresses const bridgeInstance = await BridgeContract.deployed(); - const TestStoreInstance = await TestStoreContract.deployed(); const feeRouterInstance = await FeeRouterContract.deployed(); const basicFeeHandlerInstance = await BasicFeeHandlerContract.deployed(); @@ -33,7 +32,7 @@ module.exports = async function(deployer, network) { console.log("Generic handler v1 resourceID:", "\t", currentNetworkConfig.genericV1ResourceID); // setup generic handler v1 - await bridgeInstance.adminSetGenericResource(genericHandlerV1Instance.address, currentNetworkConfig.genericV1ResourceID, TestStoreInstance.address, Helpers.blankFunctionSig, Helpers.blankFunctionDepositorOffset, Helpers.getFunctionSignature(TestStoreInstance, 'storeWithDepositor')); + await bridgeInstance.adminSetGenericResource(genericHandlerV1Instance.address, currentNetworkConfig.genericV1ResourceID, bridgeInstance.address, Helpers.blankFunctionSig, Helpers.blankFunctionDepositorOffset, Helpers.blankFunctionSig); // set resourceID for every network except current from networks config delete networksConfig[currentNetworkName] diff --git a/migrations/4_deploy_colors_example.js b/migrations/4_deploy_colors_example.js new file mode 100644 index 00000000..437df8f0 --- /dev/null +++ b/migrations/4_deploy_colors_example.js @@ -0,0 +1,14 @@ +/** + * Copyright 2020 ChainSafe Systems + * SPDX-License-Identifier: LGPL-3.0-only + */ + +const ColorsContract = artifacts.require("Colors"); + +module.exports = async function(deployer, network) { + // deploy colors example contract + await deployer.deploy(ColorsContract); + const colorsInstance = await ColorsContract.deployed(); + + console.log("Colors contract address:", "\t", colorsInstance.address); +} diff --git a/package.json b/package.json index 9f346712..dc93011f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@buildwithsygma/sygma-contracts", - "version": "1.1.0", + "version": "1.1.1", "description": "", "main": "dist/index.js", "repository": "https://github.com/sygmaprotocol/sygma-solidity.git", diff --git a/test/handlers/generic/colors_example/deposit.js b/test/handlers/generic/colors_example/deposit.js new file mode 100644 index 00000000..96556ad0 --- /dev/null +++ b/test/handlers/generic/colors_example/deposit.js @@ -0,0 +1,126 @@ +/** + * Copyright 2020 ChainSafe Systems + * SPDX-License-Identifier: LGPL-3.0-only + */ + +const TruffleAssert = require('truffle-assertions'); +const Ethers = require('ethers'); +const Helpers = require('../../../helpers'); + +const ColorsContract = artifacts.require("Colors"); +const GenericHandlerContract = artifacts.require("GenericHandlerV1"); +const WithDepositorContract = artifacts.require("WithDepositor"); +const ReturnDataContract = artifacts.require("ReturnData"); + +contract('GenericHandlerV1 colors example - [deposit]', async (accounts) => { + const originDomainID = 1; + const destinationDomainID = 2; + const expectedDepositNonce = 1; + + const depositorAddress = accounts[1]; + + const feeData = '0x'; + const destinationMaxFee = 2000000; + const hexRedColor = Helpers.toHex("0xD2042D", 32); + + let BridgeInstance; + let ColorsInstance; + + let resourceID; + let depositFunctionSignature; + let GenericHandlerInstance; + let depositData; + + beforeEach(async () => { + await Promise.all([ + BridgeInstance = await Helpers.deployBridge(originDomainID, accounts[0]), + ColorsContract.new().then(instance => ColorsInstance = instance), + WithDepositorContract.new().then(instance => WithDepositorInstance = instance), + ReturnDataContract.new().then(instance => ReturnDataInstance = instance), + ]); + + resourceID = Helpers.createResourceID(ColorsInstance.address, originDomainID) + + GenericHandlerInstance = await GenericHandlerContract.new( + BridgeInstance.address); + + await BridgeInstance.adminSetGenericResource(GenericHandlerInstance.address, resourceID, ColorsInstance.address, Helpers.blankFunctionSig, Helpers.blankFunctionDepositorOffset, Helpers.blankFunctionSig); + + depositFunctionSignature = Helpers.getFunctionSignature(ColorsInstance, 'setColor'); + + + depositData = Helpers.createGenericDepositDataV1( + depositFunctionSignature, + ColorsInstance.address, + destinationMaxFee, + depositorAddress, + hexRedColor, + false // don't append depositor for destination chain check + ); + + // set MPC address to unpause the Bridge + await BridgeInstance.endKeygen(Helpers.mpcAddress); + }); + + it('deposit can be made successfully', async () => { + await TruffleAssert.passes(BridgeInstance.deposit( + destinationDomainID, + resourceID, + depositData, + feeData, + { from: depositorAddress } + )); + }); + + it('depositEvent is emitted with expected values', async () => { + const depositTx = await BridgeInstance.deposit( + destinationDomainID, + resourceID, + depositData, + feeData, + { from: depositorAddress } + ); + + TruffleAssert.eventEmitted(depositTx, 'Deposit', (event) => { + return event.destinationDomainID.toNumber() === destinationDomainID && + event.resourceID === resourceID.toLowerCase() && + event.depositNonce.toNumber() === expectedDepositNonce && + event.user === depositorAddress && + event.data === depositData && + event.handlerResponse === null + }); + }); + + it('deposit data should be of required length', async () => { + const invalidDepositData = "0x" + "02a3d".repeat(31); + + await TruffleAssert.reverts(BridgeInstance.deposit( + destinationDomainID, + resourceID, + invalidDepositData, + feeData, + { from: depositorAddress } + ), "Incorrect data length"); + }); + + it('should revert if metadata encoded depositor does not match deposit depositor', async () => { + const invalidDepositorAddress = accounts[2]; + + const invalidDepositData = Helpers.createGenericDepositDataV1( + depositFunctionSignature, + ColorsInstance.address, + destinationMaxFee, + invalidDepositorAddress, + hexRedColor, + false // don't append depositor for destination chain check + ); + + await TruffleAssert.reverts(BridgeInstance.deposit( + destinationDomainID, + resourceID, + invalidDepositData, + feeData, + { from: depositorAddress } + ), "incorrect depositor in deposit data"); + }); +}); diff --git a/test/handlers/generic/colors_example/executeProposal.js b/test/handlers/generic/colors_example/executeProposal.js new file mode 100644 index 00000000..676a152b --- /dev/null +++ b/test/handlers/generic/colors_example/executeProposal.js @@ -0,0 +1,117 @@ +/** + * Copyright 2020 ChainSafe Systems + * SPDX-License-Identifier: LGPL-3.0-only + */ + +const TruffleAssert = require('truffle-assertions'); +const Ethers = require('ethers'); +const Helpers = require('../../../helpers'); + +const ColorsContract = artifacts.require("Colors"); +const GenericHandlerContract = artifacts.require("GenericHandlerV1"); + +contract('GenericHandlerV1 colors example - [Execute Proposal]', async (accounts) => { + const originDomainID = 1; + const destinationDomainID = 2; + const expectedDepositNonce = 1; + + const depositorAddress = accounts[1]; + const relayer1Address = accounts[2]; + const relayer2Address = accounts[3]; + + const feeData = '0x'; + const destinationMaxFee = 2000000; + const hexRedColor = Helpers.toHex("0xD2042D", 32); + + let BridgeInstance; + let ColorsInstance; + + let resourceID; + let depositFunctionSignature; + let GenericHandlerInstance; + let depositData; + let proposal; + + beforeEach(async () => { + await Promise.all([ + BridgeInstance = await Helpers.deployBridge(destinationDomainID, accounts[0]), + ColorsContract.new().then(instance => ColorsInstance = instance) + ]); + + resourceID = Helpers.createResourceID(ColorsInstance.address, originDomainID); + + GenericHandlerInstance = await GenericHandlerContract.new( + BridgeInstance.address); + + await BridgeInstance.adminSetGenericResource(GenericHandlerInstance.address, resourceID, ColorsInstance.address, Helpers.blankFunctionSig, Helpers.blankFunctionDepositorOffset, Helpers.blankFunctionSig); + + depositFunctionSignature = Helpers.getFunctionSignature(ColorsInstance, 'setColor'); + + depositData = + Helpers.createGenericDepositDataV1( + depositFunctionSignature, + ColorsInstance.address, + destinationMaxFee, + depositorAddress, + hexRedColor, + false // don't append depositor for destination chain check + ); + + proposal = { + originDomainID: originDomainID, + depositNonce: expectedDepositNonce, + data: depositData, + resourceID: resourceID + }; + + // set MPC address to unpause the Bridge + await BridgeInstance.endKeygen(Helpers.mpcAddress); + }); + + it('deposit can be executed successfully', async () => { + const proposalSignedData = await Helpers.signTypedProposal(BridgeInstance.address, [proposal]); + await TruffleAssert.passes(BridgeInstance.deposit( + originDomainID, + resourceID, + depositData, + feeData, + { from: depositorAddress } + )); + + // relayer1 executes the proposal + await TruffleAssert.passes(BridgeInstance.executeProposal( + proposal, + proposalSignedData, + { from: relayer1Address } + )); + + // Verifying color was stored in ColorsInstance contract + const storedColor = await ColorsInstance.findColor(hexRedColor); + assert.equal(storedColor, hexRedColor); + }); + + it('setColor event should be emitted', async () => { + const proposalSignedData = await Helpers.signTypedProposal(BridgeInstance.address, [proposal]); + + + await TruffleAssert.passes(BridgeInstance.deposit( + originDomainID, + resourceID, + depositData, + feeData, + { from: depositorAddress } + )); + + // relayer1 executes the proposal + const executeTx = await BridgeInstance.executeProposal( + proposal, + proposalSignedData, + { from: relayer2Address } + ); + + const internalTx = await TruffleAssert.createTransactionResult(ColorsInstance, executeTx.tx); + TruffleAssert.eventEmitted(internalTx, 'setColorEvent', event => { + return event.color === hexRedColor; + }); + }); +}); diff --git a/test/helpers.js b/test/helpers.js index 1288b243..6922d2b0 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -92,8 +92,14 @@ const createGenericDepositData = (hexMetaData) => { hexMetaData.substr(2) }; -const createGenericDepositDataV1 = (executeFunctionSignature, executeContractAddress, maxFee, depositor, executionData) => { - const metaData = toHex(depositor, 32).substr(2) + executionData.substr(2) + toHex(depositor, 32).substr(2); // append depositor address for destination chain check +const createGenericDepositDataV1 = (executeFunctionSignature, executeContractAddress, maxFee, depositor, executionData, depositorCheck = true) => { + let metaData = toHex(depositor, 32).substr(2) + executionData.substr(2); + + if(depositorCheck) { + // if "depositorCheck" is true -> append depositor address for destination chain check + metaData = metaData.concat(toHex(depositor, 32).substr(2)); + } + const metaDataLength = metaData.length / 2; return '0x' +