From bee2d34186a64d870940bd18383398878dd918f2 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 09:27:25 +0100 Subject: [PATCH 01/27] feat(test-constants): <- adds zero address to those --- test/test-constants.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-constants.js b/test/test-constants.js index f6cee54..108320b 100644 --- a/test/test-constants.js +++ b/test/test-constants.js @@ -3,4 +3,5 @@ module.exports = { ADDRESS_PROP: 'address', ORIGIN_CHAIN_ID: '0x0069c322', DESTINATION_CHAIN_ID: '0x00f34368', + ZERO_ADDRESS: '0x0000000000000000000000000000000000000000', } From a40d67ce86307b4a2e0f54f4f20fdc3dbb83bb4c Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 09:28:10 +0100 Subject: [PATCH 02/27] feat(vault): <- adds PNT address to that & fxn to change it --- contracts/Erc20Vault.sol | 16 +++++++++++++ test/change-pnt-address.test.js | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/change-pnt-address.test.js diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index af8ef90..c08e730 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -31,6 +31,11 @@ contract Erc20Vault is bytes4 public ORIGIN_CHAIN_ID; address private wEthUnwrapperAddress; + // NOTE: The following are used in the special handling of the EthPNT token, where a peg in of + // EthPNT will result in an event which will mint pPNT on the other side of the bridge, thus + // merging the PNT & EthPNT tokens for all intents and purposes. + address public PNT_ADDRESS; + event PegIn( address _tokenAddress, address _tokenSender, @@ -308,4 +313,15 @@ contract Erc20Vault is ORIGIN_CHAIN_ID = _newOriginChainId; return true; } + + function changePntAddress( + address _newAddress + ) + public + onlyPNetwork + returns (bool success) + { + PNT_ADDRESS = _newAddress; + return true; + } } diff --git a/test/change-pnt-address.test.js b/test/change-pnt-address.test.js new file mode 100644 index 0000000..3051a2f --- /dev/null +++ b/test/change-pnt-address.test.js @@ -0,0 +1,42 @@ +const { + getRandomEthAddress, + deployUpgradeableContract, +} = require('./test-utils') +const assert = require('assert') +const { ZERO_ADDRESS } = require('./test-constants') + +describe('Change PNT Address', () => { + const SAMPLE_PNT_ADDRESS = getRandomEthAddress(ethers) + const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' + const VAULT_PATH = 'contracts/Erc20Vault.sol:Erc20Vault' + + let NON_PNETWORK, VAULT_CONTRACT, NON_OWNED_VAULT_CONTRACT + + beforeEach(async () => { + const signers = await ethers.getSigners() + NON_PNETWORK = signers[1] + const dummyWethAddress = getRandomEthAddress(ethers) + VAULT_CONTRACT = await deployUpgradeableContract( + VAULT_PATH, + [ dummyWethAddress, [], SAMPLE_ORIGIN_CHAIN_ID ] + ) + NON_OWNED_VAULT_CONTRACT = VAULT_CONTRACT.connect(NON_PNETWORK) + }) + + it('pNetwork can change PNT address', async () => { + assert.strictEqual(await VAULT_CONTRACT.PNT_ADDRESS(), ZERO_ADDRESS) + await VAULT_CONTRACT.changePntAddress(SAMPLE_PNT_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.PNT_ADDRESS(), SAMPLE_PNT_ADDRESS) + }) + + it('Non pNetwork cannot change PNT address', async () => { + assert.strictEqual(await VAULT_CONTRACT.PNT_ADDRESS(), ZERO_ADDRESS) + try { + await NON_OWNED_VAULT_CONTRACT.changePntAddress(SAMPLE_PNT_ADDRESS) + assert.fail('Should not have succeeded!') + } catch (_err) { + const expectedErr = 'Caller must be PNETWORK address!' + assert(_err.message.includes(expectedErr)) + } + }) +}) From d4133032cf8510720ff16287f808a368672aae51 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 09:32:00 +0100 Subject: [PATCH 03/27] chore(package.json): <- adds `ENDPOINT` env var to compile cmd in that --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e78c60..a21bc8b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "npm run tests", "tests": "ENDPOINT=http://localhost:8545 npx hardhat test", - "compile": "npx hardhat compile", + "compile": "ENDPOINT=http://localhost:8545 npx hardhat compile", "lint": "npx eslint ." }, "repository": { From 299aa21c8c420a840d26035091c46042b66d46ee Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 09:32:27 +0100 Subject: [PATCH 04/27] feat(vault): <- adds ETHPNT address to that & fxn to change it --- contracts/Erc20Vault.sol | 12 +++++++++ test/change-ethpnt-address.test.js | 42 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 test/change-ethpnt-address.test.js diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index c08e730..2e66c53 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -35,6 +35,7 @@ contract Erc20Vault is // EthPNT will result in an event which will mint pPNT on the other side of the bridge, thus // merging the PNT & EthPNT tokens for all intents and purposes. address public PNT_ADDRESS; + address public ETHPNT_ADDRESS; event PegIn( address _tokenAddress, @@ -324,4 +325,15 @@ contract Erc20Vault is PNT_ADDRESS = _newAddress; return true; } + + function changeEthPntAddress( + address _newAddress + ) + public + onlyPNetwork + returns (bool success) + { + ETHPNT_ADDRESS = _newAddress; + return true; + } } diff --git a/test/change-ethpnt-address.test.js b/test/change-ethpnt-address.test.js new file mode 100644 index 0000000..8aa1c0a --- /dev/null +++ b/test/change-ethpnt-address.test.js @@ -0,0 +1,42 @@ +const { + getRandomEthAddress, + deployUpgradeableContract, +} = require('./test-utils') +const assert = require('assert') +const { ZERO_ADDRESS } = require('./test-constants') + +describe('Change EthPNT Address', () => { + const SAMPLE_PNT_ADDRESS = getRandomEthAddress(ethers) + const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' + const VAULT_PATH = 'contracts/Erc20Vault.sol:Erc20Vault' + + let NON_PNETWORK, VAULT_CONTRACT, NON_OWNED_VAULT_CONTRACT + + beforeEach(async () => { + const signers = await ethers.getSigners() + NON_PNETWORK = signers[1] + const dummyWethAddress = getRandomEthAddress(ethers) + VAULT_CONTRACT = await deployUpgradeableContract( + VAULT_PATH, + [ dummyWethAddress, [], SAMPLE_ORIGIN_CHAIN_ID ] + ) + NON_OWNED_VAULT_CONTRACT = VAULT_CONTRACT.connect(NON_PNETWORK) + }) + + it('pNetwork can change EthPNT address', async () => { + assert.strictEqual(await VAULT_CONTRACT.ETHPNT_ADDRESS(), ZERO_ADDRESS) + await VAULT_CONTRACT.changeEthPntAddress(SAMPLE_PNT_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.ETHPNT_ADDRESS(), SAMPLE_PNT_ADDRESS) + }) + + it('Non pNetwork cannot change EthPNT address', async () => { + assert.strictEqual(await VAULT_CONTRACT.ETHPNT_ADDRESS(), ZERO_ADDRESS) + try { + await NON_OWNED_VAULT_CONTRACT.changeEthPntAddress(SAMPLE_PNT_ADDRESS) + assert.fail('Should not have succeeded!') + } catch (_err) { + const expectedErr = 'Caller must be PNETWORK address!' + assert(_err.message.includes(expectedErr)) + } + }) +}) From 476000e6a01bd30212819ca1f634e807e62828ff Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 09:35:21 +0100 Subject: [PATCH 05/27] feat(contracts): <- adds special handling to that for ETHPNT tokens on pegging in --- contracts/Erc20Vault.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 2e66c53..0a71a92 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -30,10 +30,6 @@ contract Erc20Vault is IWETH public weth; bytes4 public ORIGIN_CHAIN_ID; address private wEthUnwrapperAddress; - - // NOTE: The following are used in the special handling of the EthPNT token, where a peg in of - // EthPNT will result in an event which will mint pPNT on the other side of the bridge, thus - // merging the PNT & EthPNT tokens for all intents and purposes. address public PNT_ADDRESS; address public ETHPNT_ADDRESS; @@ -153,7 +149,10 @@ contract Erc20Vault is require(_tokenAmount > 0, "Token amount must be greater than zero!"); IERC20Upgradeable(_tokenAddress).safeTransferFrom(msg.sender, address(this), _tokenAmount); emit PegIn( - _tokenAddress, + // NOTE: This is the special handling of the EthPNT token, where a peg in of EthPNT will + // result in an event which will mint a PNT pToken on the other side of the bridge, thus + // merging the PNT & EthPNT tokens for all intents and purposes. + _tokenAddress == ETHPNT_ADDRESS ? PNT_ADDRESS : _tokenAddress, msg.sender, _tokenAmount, _destinationAddress, From 786d0d47a0e5de78d7dd3c0b6f9ff386d50d178e Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 09:40:10 +0100 Subject: [PATCH 06/27] chore(linting): <- adds check for `only` in tests to that --- .eslintrc.js | 4 ++++ package-lock.json | 16 ++++++++++++++++ package.json | 1 + 3 files changed, 21 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 26e60c8..db34925 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,11 @@ module.exports = { "ethers": false, "upgrades": false, }, + plugins: [ + 'no-only-tests', + ], rules: { + "no-only-tests/no-only-tests": "warn", "max-len": ["error", 120, 2, { ignoreUrls: true, ignoreComments: false, diff --git a/package-lock.json b/package-lock.json index 88f835d..1adcb18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-mocha": "^5.3.0", + "eslint-plugin-no-only-tests": "^3.0.0", "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", @@ -3349,6 +3350,15 @@ "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", "dev": true }, + "node_modules/eslint-plugin-no-only-tests": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.0.0.tgz", + "integrity": "sha512-I0PeXMs1vu21ap45hey4HQCJRqpcoIvGcNTPJe+UhUm8TwjQ6//mCrDqF8q0WS6LgmRDwQ4ovQej0AQsAHb5yg==", + "dev": true, + "engines": { + "node": ">=5.0.0" + } + }, "node_modules/eslint-plugin-node": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz", @@ -20410,6 +20420,12 @@ } } }, + "eslint-plugin-no-only-tests": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.0.0.tgz", + "integrity": "sha512-I0PeXMs1vu21ap45hey4HQCJRqpcoIvGcNTPJe+UhUm8TwjQ6//mCrDqF8q0WS6LgmRDwQ4ovQej0AQsAHb5yg==", + "dev": true + }, "eslint-plugin-node": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz", diff --git a/package.json b/package.json index a21bc8b..192bbf3 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-mocha": "^5.3.0", + "eslint-plugin-no-only-tests": "^3.0.0", "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", From 7f3241dd9e9099291455c6deea72b6fccdcb2707 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 10:18:31 +0100 Subject: [PATCH 07/27] ref(contract): <- improve new constants' names in that --- contracts/Erc20Vault.sol | 12 ++++++------ test/change-ethpnt-address.test.js | 10 +++++----- test/change-pnt-address.test.js | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 0a71a92..9ed10c6 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -30,8 +30,8 @@ contract Erc20Vault is IWETH public weth; bytes4 public ORIGIN_CHAIN_ID; address private wEthUnwrapperAddress; - address public PNT_ADDRESS; - address public ETHPNT_ADDRESS; + address public PNT_TOKEN_ADDRESS; + address public ETHPNT_TOKEN_ADDRESS; event PegIn( address _tokenAddress, @@ -314,25 +314,25 @@ contract Erc20Vault is return true; } - function changePntAddress( + function changePntTokenAddress( address _newAddress ) public onlyPNetwork returns (bool success) { - PNT_ADDRESS = _newAddress; + PNT_TOKEN_ADDRESS = _newAddress; return true; } - function changeEthPntAddress( + function changeEthPntTokenAddress( address _newAddress ) public onlyPNetwork returns (bool success) { - ETHPNT_ADDRESS = _newAddress; + ETHPNT_TOKEN_ADDRESS = _newAddress; return true; } } diff --git a/test/change-ethpnt-address.test.js b/test/change-ethpnt-address.test.js index 8aa1c0a..739b770 100644 --- a/test/change-ethpnt-address.test.js +++ b/test/change-ethpnt-address.test.js @@ -24,15 +24,15 @@ describe('Change EthPNT Address', () => { }) it('pNetwork can change EthPNT address', async () => { - assert.strictEqual(await VAULT_CONTRACT.ETHPNT_ADDRESS(), ZERO_ADDRESS) - await VAULT_CONTRACT.changeEthPntAddress(SAMPLE_PNT_ADDRESS) - assert.strictEqual(await VAULT_CONTRACT.ETHPNT_ADDRESS(), SAMPLE_PNT_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.ETHPNT_TOKEN_ADDRESS(), ZERO_ADDRESS) + await VAULT_CONTRACT.changeEthPntTokenAddress(SAMPLE_PNT_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.ETHPNT_TOKEN_ADDRESS(), SAMPLE_PNT_ADDRESS) }) it('Non pNetwork cannot change EthPNT address', async () => { - assert.strictEqual(await VAULT_CONTRACT.ETHPNT_ADDRESS(), ZERO_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.ETHPNT_TOKEN_ADDRESS(), ZERO_ADDRESS) try { - await NON_OWNED_VAULT_CONTRACT.changeEthPntAddress(SAMPLE_PNT_ADDRESS) + await NON_OWNED_VAULT_CONTRACT.changeEthPntTokenAddress(SAMPLE_PNT_ADDRESS) assert.fail('Should not have succeeded!') } catch (_err) { const expectedErr = 'Caller must be PNETWORK address!' diff --git a/test/change-pnt-address.test.js b/test/change-pnt-address.test.js index 3051a2f..0ee110a 100644 --- a/test/change-pnt-address.test.js +++ b/test/change-pnt-address.test.js @@ -24,15 +24,15 @@ describe('Change PNT Address', () => { }) it('pNetwork can change PNT address', async () => { - assert.strictEqual(await VAULT_CONTRACT.PNT_ADDRESS(), ZERO_ADDRESS) - await VAULT_CONTRACT.changePntAddress(SAMPLE_PNT_ADDRESS) - assert.strictEqual(await VAULT_CONTRACT.PNT_ADDRESS(), SAMPLE_PNT_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), ZERO_ADDRESS) + await VAULT_CONTRACT.changePntTokenAddress(SAMPLE_PNT_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), SAMPLE_PNT_ADDRESS) }) it('Non pNetwork cannot change PNT address', async () => { - assert.strictEqual(await VAULT_CONTRACT.PNT_ADDRESS(), ZERO_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), ZERO_ADDRESS) try { - await NON_OWNED_VAULT_CONTRACT.changePntAddress(SAMPLE_PNT_ADDRESS) + await NON_OWNED_VAULT_CONTRACT.changePntTokenAddress(SAMPLE_PNT_ADDRESS) assert.fail('Should not have succeeded!') } catch (_err) { const expectedErr = 'Caller must be PNETWORK address!' From 6e49ecd79e66cdcb95df5d3606227f1f68a20e38 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 10:18:47 +0100 Subject: [PATCH 08/27] feat(contract): <- adds logic to handle ETHPNT peg ins & tests --- contracts/Erc20Vault.sol | 2 +- test/pegging-in-ethpnt.test.js | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 test/pegging-in-ethpnt.test.js diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 9ed10c6..8fb533d 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -152,7 +152,7 @@ contract Erc20Vault is // NOTE: This is the special handling of the EthPNT token, where a peg in of EthPNT will // result in an event which will mint a PNT pToken on the other side of the bridge, thus // merging the PNT & EthPNT tokens for all intents and purposes. - _tokenAddress == ETHPNT_ADDRESS ? PNT_ADDRESS : _tokenAddress, + _tokenAddress == ETHPNT_TOKEN_ADDRESS ? PNT_TOKEN_ADDRESS : _tokenAddress, msg.sender, _tokenAmount, _destinationAddress, diff --git a/test/pegging-in-ethpnt.test.js b/test/pegging-in-ethpnt.test.js new file mode 100644 index 0000000..bdbe559 --- /dev/null +++ b/test/pegging-in-ethpnt.test.js @@ -0,0 +1,92 @@ +const { + getRandomEthAddress, + deployUpgradeableContract, + deployNonUpgradeableContract, +} = require('./test-utils') +const assert = require('assert') +const { prop } = require('ramda') +const { BigNumber } = require('ethers') +const { ADDRESS_PROP } = require('./test-constants') + +describe('Pegging In EthPNT Tests', () => { + const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' + const VAULT_PATH = 'contracts/Erc20Vault.sol:Erc20Vault' + + let TOKEN_HOLDER, + VAULT_CONTRACT, + PNT_TOKEN_ADDRESS, + PNT_TOKEN_CONTRACT, + TOKEN_HOLDER_ADDRESS, + ETHPNT_TOKEN_ADDRESS, + ETHPNT_TOKEN_CONTRACT, + VAULT_CONTRACT_ADDRESS + + beforeEach(async () => { + const TOKEN_HOLDER_BALANCE = 1e6 + const signers = await ethers.getSigners() + const WETH_ADDRESS = getRandomEthAddress() + + // NOTE: Create a token holder for us to play with.. + TOKEN_HOLDER = signers[1] + TOKEN_HOLDER_ADDRESS = prop(ADDRESS_PROP, TOKEN_HOLDER) + + // NOTE: Deploy the vault contract itself... + VAULT_CONTRACT = await deployUpgradeableContract(VAULT_PATH, [ WETH_ADDRESS, [], SAMPLE_ORIGIN_CHAIN_ID ]) + VAULT_CONTRACT_ADDRESS = prop(ADDRESS_PROP, VAULT_CONTRACT) + + // Deploy two contracts to mock the PNT and ETHPNT tokens... + PNT_TOKEN_CONTRACT = await deployNonUpgradeableContract('contracts/test-contracts/Erc20Token.sol:Erc20Token') + ETHPNT_TOKEN_CONTRACT = await deployNonUpgradeableContract('contracts/test-contracts/Erc20Token.sol:Erc20Token') + PNT_TOKEN_ADDRESS = prop(ADDRESS_PROP, PNT_TOKEN_CONTRACT) + ETHPNT_TOKEN_ADDRESS = prop(ADDRESS_PROP, ETHPNT_TOKEN_CONTRACT) + + // NOTE: Supply our token holder with some ETHPNT to peg in... + await ETHPNT_TOKEN_CONTRACT.transfer(TOKEN_HOLDER_ADDRESS, TOKEN_HOLDER_BALANCE) + + // NOTE: Assert that the holder actually has those tokens... + let tokenHolderEthPntBalance = await ETHPNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + assert(tokenHolderEthPntBalance.eq(BigNumber.from(TOKEN_HOLDER_BALANCE))) + + // NOTE: Add the EthPNT token as a supported token in the vault... + await VAULT_CONTRACT.addSupportedToken(ETHPNT_TOKEN_ADDRESS) + assert(await VAULT_CONTRACT.isTokenSupported(ETHPNT_TOKEN_ADDRESS)) + + // NOTE approve the vault to spend the EthPNT tokens the token holder holds, so they can be pegged in... + await ETHPNT_TOKEN_CONTRACT.connect(TOKEN_HOLDER).approve(VAULT_CONTRACT_ADDRESS, TOKEN_HOLDER_BALANCE) + + // NOTE: Set the PNT & EthPNT token addresses correctly in the vault... + await VAULT_CONTRACT.changePntTokenAddress(PNT_TOKEN_ADDRESS) + await VAULT_CONTRACT.changeEthPntTokenAddress(ETHPNT_TOKEN_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), PNT_TOKEN_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.ETHPNT_TOKEN_ADDRESS(), ETHPNT_TOKEN_ADDRESS) + }) + + it('Pegging in EthPNT token should fire event with token address as PNT token', async () => { + const tokenAmount = 1337 + const destinationAddress = getRandomEthAddress() + const userData = '0xc0ffee' + const destinationChainId = '0xffffffff' + + // NOTE: We have to call the fxn this way because its overloaded in the contract... + const tx = await VAULT_CONTRACT.connect(TOKEN_HOLDER)['pegIn(uint256,address,string,bytes,bytes4)']( + tokenAmount, + ETHPNT_TOKEN_ADDRESS, + destinationAddress, + userData, + destinationChainId, + ) + const txReceipt = await tx.wait() + const expectedNumEvents = 3 + assert.strictEqual(txReceipt.events.length, expectedNumEvents) + const pegInEvent = txReceipt.events[expectedNumEvents - 1] + + // NOTE: Assert the event args... + assert.strictEqual(pegInEvent.args[0], PNT_TOKEN_ADDRESS) // NOTE: This is the one we care about here! + assert.strictEqual(pegInEvent.args[1], TOKEN_HOLDER_ADDRESS) + assert(pegInEvent.args[2].eq(tokenAmount)) + assert.strictEqual(pegInEvent.args[3], destinationAddress) + assert.strictEqual(pegInEvent.args[4], userData) + assert.strictEqual(pegInEvent.args[5], SAMPLE_ORIGIN_CHAIN_ID) + assert.strictEqual(pegInEvent.args[6], destinationChainId) + }) +}) From 18ee773be886902e8a67060ed80991e118fe53bc Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 10:34:42 +0100 Subject: [PATCH 09/27] feat(ethpnt-pegins): <- adds check to normalized peg in address to that --- contracts/Erc20Vault.sol | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 8fb533d..cc9ea7e 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -148,11 +148,18 @@ contract Erc20Vault is { require(_tokenAmount > 0, "Token amount must be greater than zero!"); IERC20Upgradeable(_tokenAddress).safeTransferFrom(msg.sender, address(this), _tokenAmount); + + // NOTE: This is the special handling of the EthPNT token, where a peg in of EthPNT will + // result in an event which will mint a PNT pToken on the other side of the bridge, thus + // merging the PNT & EthPNT tokens for all intents and purposes. + address normalizedTokenAddress = _tokenAddress == ETHPNT_TOKEN_ADDRESS + ? PNT_TOKEN_ADDRESS + : _tokenAddress; + + require(normalizedTokenAddress != address(0), "`PNT_TOKEN_ADDRESS` is set to zero address!"); + emit PegIn( - // NOTE: This is the special handling of the EthPNT token, where a peg in of EthPNT will - // result in an event which will mint a PNT pToken on the other side of the bridge, thus - // merging the PNT & EthPNT tokens for all intents and purposes. - _tokenAddress == ETHPNT_TOKEN_ADDRESS ? PNT_TOKEN_ADDRESS : _tokenAddress, + normalizedTokenAddress, msg.sender, _tokenAmount, _destinationAddress, @@ -160,6 +167,7 @@ contract Erc20Vault is ORIGIN_CHAIN_ID, _destinationChainId ); + return true; } From 18b938e9d0aaef6500eade9a6f31135472917158 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 10:42:12 +0100 Subject: [PATCH 10/27] feat(tests): <- adds test for normalized PNT address being zero address --- test/pegging-in-ethpnt.test.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/pegging-in-ethpnt.test.js b/test/pegging-in-ethpnt.test.js index bdbe559..aaab5d3 100644 --- a/test/pegging-in-ethpnt.test.js +++ b/test/pegging-in-ethpnt.test.js @@ -3,10 +3,13 @@ const { deployUpgradeableContract, deployNonUpgradeableContract, } = require('./test-utils') +const { + ZERO_ADDRESS, + ADDRESS_PROP, +} = require('./test-constants') const assert = require('assert') const { prop } = require('ramda') const { BigNumber } = require('ethers') -const { ADDRESS_PROP } = require('./test-constants') describe('Pegging In EthPNT Tests', () => { const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' @@ -89,4 +92,28 @@ describe('Pegging In EthPNT Tests', () => { assert.strictEqual(pegInEvent.args[5], SAMPLE_ORIGIN_CHAIN_ID) assert.strictEqual(pegInEvent.args[6], destinationChainId) }) + + it('Should fail to peg in if `PNT_TOKEN_ADDRESS` is set to zero', async () => { + await VAULT_CONTRACT.changePntTokenAddress(ZERO_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), ZERO_ADDRESS) + + const tokenAmount = 1337 + const destinationAddress = getRandomEthAddress() + const userData = '0xc0ffee' + const destinationChainId = '0xffffffff' + + try { + await VAULT_CONTRACT.connect(TOKEN_HOLDER)['pegIn(uint256,address,string,bytes,bytes4)']( + tokenAmount, + ETHPNT_TOKEN_ADDRESS, + destinationAddress, + userData, + destinationChainId, + ) + assert.fail('Should not have succeeded!') + } catch (_err) { + const expectedError = '`PNT_TOKEN_ADDRESS` is set to zero address!' + assert(_err.message.includes(expectedError)) + } + }) }) From 3b1284d059fbc898873fc91d16997249fd255657 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 11:00:24 +0100 Subject: [PATCH 11/27] feat(contract): <- adds function to handle peg out token transfers --- contracts/Erc20Vault.sol | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index cc9ea7e..db292be 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -281,7 +281,7 @@ contract Erc20Vault is if (_tokenAddress == address(weth)) { pegOutWeth(_tokenRecipient, _tokenAmount, ""); } else { - IERC20Upgradeable(_tokenAddress).safeTransfer(_tokenRecipient, _tokenAmount); + handlePegOutTokenTransfer(_tokenAddress, _tokenRecipient, _tokenAmount, "", false); } return true; } @@ -301,14 +301,31 @@ contract Erc20Vault is } else { address erc777Address = _erc1820.getInterfaceImplementer(_tokenAddress, Erc777Token_INTERFACE_HASH); if (erc777Address == address(0)) { - return pegOut(_tokenRecipient, _tokenAddress, _tokenAmount); + return handlePegOutTokenTransfer(_tokenAddress, _tokenRecipient, _tokenAmount, "", false); } else { - IERC777Upgradeable(erc777Address).send(_tokenRecipient, _tokenAmount, _userData); - return true; + return handlePegOutTokenTransfer(_tokenAddress, _tokenRecipient, _tokenAmount, _userData, true); } } } + function handlePegOutTokenTransfer( + address _tokenAddress, + address _tokenRecipient, + uint256 _tokenAmount, + bytes memory _userData, + bool _isErc777Token + ) + internal + returns (bool success) + { + if (_isErc777Token) { + IERC777Upgradeable(_tokenAddress).send(_tokenRecipient, _tokenAmount, _userData); + } else { + IERC20Upgradeable(_tokenAddress).safeTransfer(_tokenRecipient, _tokenAmount); + } + return true; + } + receive() external payable { } function changeOriginChainId( From cd199e90c020917a8f2877e81415432d6d272aaf Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 11:54:26 +0100 Subject: [PATCH 12/27] feat(contract): <- adds special handling for PNT token peg outs --- contracts/Erc20Vault.sol | 75 ++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index db292be..4e5ec35 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -21,13 +21,13 @@ contract Erc20Vault is using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; IERC1820RegistryUpgradeable constant private _erc1820 = IERC1820RegistryUpgradeable( 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 - ); + ); //FIXME The name of this should be `SCREAMING_SNAKE_CASE`. Will it break storage slot upgradeability? bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); bytes32 constant private Erc777Token_INTERFACE_HASH = keccak256("ERC777Token"); - EnumerableSetUpgradeable.AddressSet private supportedTokens; + EnumerableSetUpgradeable.AddressSet private supportedTokens; // FIXME Ibid. address public PNETWORK; - IWETH public weth; + IWETH public weth; // FIXME Ibid. bytes4 public ORIGIN_CHAIN_ID; address private wEthUnwrapperAddress; address public PNT_TOKEN_ADDRESS; @@ -250,7 +250,7 @@ contract Erc20Vault is bytes memory _userData ) internal - returns (bool) + returns (bool success) { // NOTE: This is a mitigation for the breaking changes introduced // by the Istanbul hard fork which caused the [out of gas] errors @@ -276,14 +276,11 @@ contract Erc20Vault is ) public onlyPNetwork - returns (bool) + returns (bool success) { - if (_tokenAddress == address(weth)) { - pegOutWeth(_tokenRecipient, _tokenAmount, ""); - } else { - handlePegOutTokenTransfer(_tokenAddress, _tokenRecipient, _tokenAmount, "", false); - } - return true; + return _tokenAddress == address(weth) + ? pegOutWeth(_tokenRecipient, _tokenAmount, "") + : handlePegOutTokenTransfers(_tokenAddress, _tokenRecipient, _tokenAmount, ""); } function pegOut( @@ -296,36 +293,62 @@ contract Erc20Vault is onlyPNetwork returns (bool success) { - if (_tokenAddress == address(weth)) { - pegOutWeth(_tokenRecipient, _tokenAmount, _userData); - } else { - address erc777Address = _erc1820.getInterfaceImplementer(_tokenAddress, Erc777Token_INTERFACE_HASH); - if (erc777Address == address(0)) { - return handlePegOutTokenTransfer(_tokenAddress, _tokenRecipient, _tokenAmount, "", false); - } else { - return handlePegOutTokenTransfer(_tokenAddress, _tokenRecipient, _tokenAmount, _userData, true); - } - } + return _tokenAddress == address(weth) + ? pegOutWeth(_tokenRecipient, _tokenAmount, _userData) + : handlePegOutTokenTransfers(_tokenAddress, _tokenRecipient, _tokenAmount, _userData); } - function handlePegOutTokenTransfer( + function handlePegOutTokenTransfers( address _tokenAddress, address _tokenRecipient, uint256 _tokenAmount, - bytes memory _userData, - bool _isErc777Token + bytes memory _userData ) internal returns (bool success) { - if (_isErc777Token) { + address maybeErc777Address = _erc1820.getInterfaceImplementer(_tokenAddress, Erc777Token_INTERFACE_HASH); + if (maybeErc777Address != address(0)) { + // NOTE: Use the ERC777 `send` function so that ERC777 hooks are called... IERC777Upgradeable(_tokenAddress).send(_tokenRecipient, _tokenAmount, _userData); } else { - IERC20Upgradeable(_tokenAddress).safeTransfer(_tokenRecipient, _tokenAmount); + // NOTE: Otherwise, we use standard ERC20 transfer function instead. + if (_tokenAddress == PNT_TOKEN_ADDRESS) { + handlePntPegOut(_tokenRecipient, _tokenAmount, _userData); + } else { + // NOTE: No special handling required for this token... + IERC20Upgradeable(_tokenAddress).safeTransfer(_tokenRecipient, _tokenAmount); + } } return true; } + function handlePntPegOut( + address _tokenRecipient, + uint256 _tokenAmount, + bytes memory _userData + ) + internal + { + IERC20Upgradeable pntContract = IERC20Upgradeable(PNT_TOKEN_ADDRESS); + IERC20Upgradeable ethPntContract = IERC20Upgradeable(ETHPNT_TOKEN_ADDRESS); + uint256 pntTokenBalance = pntContract.balanceOf(address(this)); + + if (_tokenAmount <= pntTokenBalance) { + // NOTE: If we can peg out entirely with PNT tokens, we do so... + pntContract.safeTransfer(_tokenRecipient, _tokenAmount); + } else if (pntTokenBalance == 0) { + // NOTE: Here we must peg out entirely with ETHPNT tokens instead... + ethPntContract.safeTransfer(_tokenRecipient, _tokenAmount); + } else { + // NOTE: And so here we must peg out the total using as must PNT as possible, with + // the remainder being EthPNT... + uint256 ethPntAmount = _tokenAmount - pntTokenBalance; + pntContract.safeTransfer(_tokenRecipient, _tokenAmount); + ethPntContract.safeTransfer(_tokenRecipient, ethPntAmount); + } + } + receive() external payable { } function changeOriginChainId( From e8199d5dcbbeae27a7f23a1b3874080f3299e08c Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 12:04:04 +0100 Subject: [PATCH 13/27] ref(contract): <- simplify logic during pegouts --- contracts/Erc20Vault.sol | 41 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 4e5ec35..d80e5bc 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -250,7 +250,7 @@ contract Erc20Vault is bytes memory _userData ) internal - returns (bool success) + returns (bool) { // NOTE: This is a mitigation for the breaking changes introduced // by the Istanbul hard fork which caused the [out of gas] errors @@ -280,7 +280,7 @@ contract Erc20Vault is { return _tokenAddress == address(weth) ? pegOutWeth(_tokenRecipient, _tokenAmount, "") - : handlePegOutTokenTransfers(_tokenAddress, _tokenRecipient, _tokenAmount, ""); + : pegOutTokens(_tokenAddress, _tokenRecipient, _tokenAmount, ""); } function pegOut( @@ -295,10 +295,10 @@ contract Erc20Vault is { return _tokenAddress == address(weth) ? pegOutWeth(_tokenRecipient, _tokenAmount, _userData) - : handlePegOutTokenTransfers(_tokenAddress, _tokenRecipient, _tokenAmount, _userData); + : pegOutTokens(_tokenAddress, _tokenRecipient, _tokenAmount, _userData); } - function handlePegOutTokenTransfers( + function pegOutTokens( address _tokenAddress, address _tokenRecipient, uint256 _tokenAmount, @@ -307,43 +307,44 @@ contract Erc20Vault is internal returns (bool success) { - address maybeErc777Address = _erc1820.getInterfaceImplementer(_tokenAddress, Erc777Token_INTERFACE_HASH); - if (maybeErc777Address != address(0)) { - // NOTE: Use the ERC777 `send` function so that ERC777 hooks are called... + if (_tokenAddress == PNT_TOKEN_ADDRESS) { + handlePntPegOut(_tokenRecipient, _tokenAmount); + } else if (tokenIsErc777(_tokenAddress)) { + // NOTE: This is an ERC777 token, so let's use its `send` function so that hooks are called... IERC777Upgradeable(_tokenAddress).send(_tokenRecipient, _tokenAmount, _userData); } else { // NOTE: Otherwise, we use standard ERC20 transfer function instead. - if (_tokenAddress == PNT_TOKEN_ADDRESS) { - handlePntPegOut(_tokenRecipient, _tokenAmount, _userData); - } else { - // NOTE: No special handling required for this token... - IERC20Upgradeable(_tokenAddress).safeTransfer(_tokenRecipient, _tokenAmount); - } + IERC20Upgradeable(_tokenAddress).safeTransfer(_tokenRecipient, _tokenAmount); } return true; } + function tokenIsErc777(address _tokenAddress) view internal returns (bool) { + return _erc1820.getInterfaceImplementer(_tokenAddress, Erc777Token_INTERFACE_HASH) != address(0); + } + function handlePntPegOut( address _tokenRecipient, - uint256 _tokenAmount, - bytes memory _userData + uint256 _tokenAmount ) internal { IERC20Upgradeable pntContract = IERC20Upgradeable(PNT_TOKEN_ADDRESS); IERC20Upgradeable ethPntContract = IERC20Upgradeable(ETHPNT_TOKEN_ADDRESS); - uint256 pntTokenBalance = pntContract.balanceOf(address(this)); - if (_tokenAmount <= pntTokenBalance) { + // NOTE: First we need to know how much PNT this vault holds... + uint256 vaultPntTokenBalance = pntContract.balanceOf(address(this)); + + if (_tokenAmount <= vaultPntTokenBalance) { // NOTE: If we can peg out entirely with PNT tokens, we do so... pntContract.safeTransfer(_tokenRecipient, _tokenAmount); - } else if (pntTokenBalance == 0) { + } else if (vaultPntTokenBalance == 0) { // NOTE: Here we must peg out entirely with ETHPNT tokens instead... ethPntContract.safeTransfer(_tokenRecipient, _tokenAmount); } else { // NOTE: And so here we must peg out the total using as must PNT as possible, with - // the remainder being EthPNT... - uint256 ethPntAmount = _tokenAmount - pntTokenBalance; + // the remainder being sent as EthPNT... + uint256 ethPntAmount = _tokenAmount - vaultPntTokenBalance; pntContract.safeTransfer(_tokenRecipient, _tokenAmount); ethPntContract.safeTransfer(_tokenRecipient, ethPntAmount); } From a283ed528ab0b2617a90438ac12f9d1de11ce11e Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 13:47:59 +0100 Subject: [PATCH 14/27] fix(pnt-peg-out): <- use correct EthPNT calculation in that --- contracts/Erc20Vault.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index d80e5bc..f8003ba 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -344,9 +344,8 @@ contract Erc20Vault is } else { // NOTE: And so here we must peg out the total using as must PNT as possible, with // the remainder being sent as EthPNT... - uint256 ethPntAmount = _tokenAmount - vaultPntTokenBalance; - pntContract.safeTransfer(_tokenRecipient, _tokenAmount); - ethPntContract.safeTransfer(_tokenRecipient, ethPntAmount); + pntContract.safeTransfer(_tokenRecipient, vaultPntTokenBalance); + ethPntContract.safeTransfer(_tokenRecipient, _tokenAmount - vaultPntTokenBalance); } } From ff8e5a8085f46047e550469a43cb1639bc4f0071 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Tue, 27 Sep 2022 13:48:44 +0100 Subject: [PATCH 15/27] feat(ethpnt): <- adds tests for that --- test/pegging-out-pnt.test.js | 172 +++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 test/pegging-out-pnt.test.js diff --git a/test/pegging-out-pnt.test.js b/test/pegging-out-pnt.test.js new file mode 100644 index 0000000..9efb093 --- /dev/null +++ b/test/pegging-out-pnt.test.js @@ -0,0 +1,172 @@ +const { + getRandomEthAddress, + deployUpgradeableContract, + deployNonUpgradeableContract, +} = require('./test-utils') +const assert = require('assert') +const { prop } = require('ramda') +const { ADDRESS_PROP } = require('./test-constants') + +describe('Pegging Out PNT Tests', () => { + const VAULT_TOKEN_APPROVAL_AMOUNT = 1e6 + const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' + const VAULT_PATH = 'contracts/Erc20Vault.sol:Erc20Vault' + + let TOKEN_HOLDER, + VAULT_CONTRACT, + PNT_TOKEN_ADDRESS, + PNT_TOKEN_CONTRACT, + TOKEN_HOLDER_ADDRESS, + ETHPNT_TOKEN_ADDRESS, + ETHPNT_TOKEN_CONTRACT, + VAULT_CONTRACT_ADDRESS + + beforeEach(async () => { + const signers = await ethers.getSigners() + const WETH_ADDRESS = getRandomEthAddress() + + // NOTE: Create a token holder for us to play with.. + TOKEN_HOLDER = signers[1] + TOKEN_HOLDER_ADDRESS = prop(ADDRESS_PROP, TOKEN_HOLDER) + + // NOTE: Deploy the vault contract itself... + VAULT_CONTRACT = await deployUpgradeableContract(VAULT_PATH, [ WETH_ADDRESS, [], SAMPLE_ORIGIN_CHAIN_ID ]) + VAULT_CONTRACT_ADDRESS = prop(ADDRESS_PROP, VAULT_CONTRACT) + + // Deploy two contracts to mock the PNT and ETHPNT tokens... + PNT_TOKEN_CONTRACT = await deployNonUpgradeableContract('contracts/test-contracts/Erc20Token.sol:Erc20Token') + ETHPNT_TOKEN_CONTRACT = await deployNonUpgradeableContract('contracts/test-contracts/Erc20Token.sol:Erc20Token') + PNT_TOKEN_ADDRESS = prop(ADDRESS_PROP, PNT_TOKEN_CONTRACT) + ETHPNT_TOKEN_ADDRESS = prop(ADDRESS_PROP, ETHPNT_TOKEN_CONTRACT) + + // NOTE: Add the EthPNT token as a supported token in the vault... + await VAULT_CONTRACT.addSupportedToken(ETHPNT_TOKEN_ADDRESS) + assert(await VAULT_CONTRACT.isTokenSupported(ETHPNT_TOKEN_ADDRESS)) + + // NOTE approve the vault to spend the EthPNT tokens the token holder holds, so they can be pegged in... + await ETHPNT_TOKEN_CONTRACT + .connect(TOKEN_HOLDER) + .approve(VAULT_CONTRACT_ADDRESS, VAULT_TOKEN_APPROVAL_AMOUNT) + + // NOTE: Set the PNT & EthPNT token addresses correctly in the vault... + await VAULT_CONTRACT.changePntTokenAddress(PNT_TOKEN_ADDRESS) + await VAULT_CONTRACT.changeEthPntTokenAddress(ETHPNT_TOKEN_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), PNT_TOKEN_ADDRESS) + assert.strictEqual(await VAULT_CONTRACT.ETHPNT_TOKEN_ADDRESS(), ETHPNT_TOKEN_ADDRESS) + }) + + it('Should peg out PNT entirely in PNT if vault PNT balance is sufficient', async () => { + const pegOutAmount = 1337 + + // NOTE: Give the vault sufficient balance of the PNT token for our pegout.... + PNT_TOKEN_CONTRACT.transfer(VAULT_CONTRACT_ADDRESS, pegOutAmount * 2) + + // NOTE: Assert the vault's balances before... + const vaultPntBalanceBefore = await PNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + const vaultEthPntBalanceBefore = await ETHPNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + assert(vaultPntBalanceBefore.eq(pegOutAmount * 2)) + assert(vaultEthPntBalanceBefore.eq(0)) + + // NOTE: Assert the token holder's balances before... + const tokenHolderPntBalanceBefore = await PNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + const tokenHolderEthPntBalanceBefore = await ETHPNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + assert(tokenHolderPntBalanceBefore.eq(0)) + assert(tokenHolderEthPntBalanceBefore.eq(0)) + + // NOTE: We have to call the `pegOut` fxn this way because its overloaded in the contract... + await VAULT_CONTRACT['pegOut(address,address,uint256)']( + TOKEN_HOLDER_ADDRESS, + PNT_TOKEN_ADDRESS, + pegOutAmount, + ) + + // Assert the vault balances have changed as expected... + const vaultPntBalanceAfter = await PNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + const vaultEthPntBalanceAfter = await ETHPNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + assert(vaultPntBalanceAfter.eq(vaultPntBalanceBefore - pegOutAmount)) + assert(vaultEthPntBalanceAfter.eq(0)) + + // NOTE: Assert the token holder's balances have changed as expected + const tokenHolderPntBalanceAfter = await PNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + const tokenHolderEthPntBalanceAfter = await ETHPNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + assert(tokenHolderPntBalanceAfter.eq(pegOutAmount)) + assert(tokenHolderEthPntBalanceAfter.eq(0)) + }) + + it('Should peg out PNT entirely in EthPNT if vault PNT balance is zero', async () => { + const pegOutAmount = 1337 + + // NOTE: Give the vault sufficient balance of the EthPNT token for our pegout.... + ETHPNT_TOKEN_CONTRACT.transfer(VAULT_CONTRACT_ADDRESS, pegOutAmount * 2) + + // NOTE: Assert the vault's balances before... + const vaultPntBalanceBefore = await PNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + const vaultEthPntBalanceBefore = await ETHPNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + assert(vaultPntBalanceBefore.eq(0)) + assert(vaultEthPntBalanceBefore.eq(pegOutAmount * 2)) + + // NOTE: Assert the token holder's balances before... + const tokenHolderPntBalanceBefore = await PNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + const tokenHolderEthPntBalanceBefore = await ETHPNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + assert(tokenHolderPntBalanceBefore.eq(0)) + assert(tokenHolderEthPntBalanceBefore.eq(0)) + + // NOTE: We have to call the `pegOut` fxn this way because its overloaded in the contract... + await VAULT_CONTRACT['pegOut(address,address,uint256)']( + TOKEN_HOLDER_ADDRESS, + PNT_TOKEN_ADDRESS, + pegOutAmount, + ) + + // Assert the vault balances have changed as expected... + const vaultPntBalanceAfter = await PNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + const vaultEthPntBalanceAfter = await ETHPNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + assert(vaultPntBalanceAfter.eq(0)) + assert(vaultEthPntBalanceAfter.eq(vaultEthPntBalanceBefore - pegOutAmount)) + + // NOTE: Assert the token holder's balances have changed as expected + const tokenHolderPntBalanceAfter = await PNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + const tokenHolderEthPntBalanceAfter = await ETHPNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + assert(tokenHolderPntBalanceAfter.eq(0)) + assert(tokenHolderEthPntBalanceAfter.eq(pegOutAmount)) + }) + + it('Should peg out PNT in both PNT & EthPNT if vault PNT balance is not sufficient but not zero', async () => { + const pegOutAmount = 1337 + const insufficientPntAmount = Math.floor(pegOutAmount / 2) + const expectedEthPntAmount = pegOutAmount - insufficientPntAmount + + // NOTE: Give the vault insufficient balance of the PNT token for our pegout.... + PNT_TOKEN_CONTRACT.transfer(VAULT_CONTRACT_ADDRESS, insufficientPntAmount) + + // NOTE: Give the vault some balance of the EthPNT token to complete our pegout.... + ETHPNT_TOKEN_CONTRACT.transfer(VAULT_CONTRACT_ADDRESS, pegOutAmount * 2) + + // NOTE: Assert the vault's balances before... + const vaultPntBalanceBefore = await PNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + const vaultEthPntBalanceBefore = await ETHPNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + assert(vaultPntBalanceBefore.eq(insufficientPntAmount)) + assert(vaultEthPntBalanceBefore.eq(pegOutAmount * 2)) + + // NOTE: Assert the token holder's balances before... + const tokenHolderPntBalanceBefore = await PNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + const tokenHolderEthPntBalanceBefore = await ETHPNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + assert(tokenHolderPntBalanceBefore.eq(0)) + assert(tokenHolderEthPntBalanceBefore.eq(0)) + + // NOTE: We have to call the `pegOut` fxn this way because its overloaded in the contract... + await VAULT_CONTRACT['pegOut(address,address,uint256)'](TOKEN_HOLDER_ADDRESS, PNT_TOKEN_ADDRESS, pegOutAmount) + + // Assert the vault balances have changed as expected... + const vaultPntBalanceAfter = await PNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + const vaultEthPntBalanceAfter = await ETHPNT_TOKEN_CONTRACT.balanceOf(VAULT_CONTRACT_ADDRESS) + assert(vaultPntBalanceAfter.eq(0)) + assert(vaultEthPntBalanceAfter.eq(vaultEthPntBalanceBefore - expectedEthPntAmount)) + + // NOTE: Assert the token holder's balances have changed as expected + const tokenHolderPntBalanceAfter = await PNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + const tokenHolderEthPntBalanceAfter = await ETHPNT_TOKEN_CONTRACT.balanceOf(TOKEN_HOLDER_ADDRESS) + assert(tokenHolderPntBalanceAfter.eq(insufficientPntAmount)) + assert(tokenHolderEthPntBalanceAfter.eq(expectedEthPntAmount)) + }) +}) From 5200e031069fd3fcaf78a720680cbda1b2694d1c Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 10:06:19 +0100 Subject: [PATCH 16/27] fix(contract): <- update error message in that for accuracy --- contracts/Erc20Vault.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index f8003ba..15575b9 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -156,7 +156,7 @@ contract Erc20Vault is ? PNT_TOKEN_ADDRESS : _tokenAddress; - require(normalizedTokenAddress != address(0), "`PNT_TOKEN_ADDRESS` is set to zero address!"); + require(normalizedTokenAddress != address(0), "`normalizedTokenAddress` is set to zero address!"); emit PegIn( normalizedTokenAddress, From 808cb43def72425014a23770671a830264eceefb Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 10:10:02 +0100 Subject: [PATCH 17/27] feat(contract): <- the PNT is ERC777, so use the send with `userData` to transfer it instead --- contracts/Erc20Vault.sol | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 15575b9..fba4007 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -308,7 +308,7 @@ contract Erc20Vault is returns (bool success) { if (_tokenAddress == PNT_TOKEN_ADDRESS) { - handlePntPegOut(_tokenRecipient, _tokenAmount); + handlePntPegOut(_tokenRecipient, _tokenAmount, _userData); } else if (tokenIsErc777(_tokenAddress)) { // NOTE: This is an ERC777 token, so let's use its `send` function so that hooks are called... IERC777Upgradeable(_tokenAddress).send(_tokenRecipient, _tokenAmount, _userData); @@ -325,11 +325,14 @@ contract Erc20Vault is function handlePntPegOut( address _tokenRecipient, - uint256 _tokenAmount + uint256 _tokenAmount, + bytes memory _userData ) internal { - IERC20Upgradeable pntContract = IERC20Upgradeable(PNT_TOKEN_ADDRESS); + // NOTE: The PNT contract is ERC777... + IERC777Upgradeable pntContract = IERC777Upgradeable(PNT_TOKEN_ADDRESS); + // NOTE: Whilst the EthPNT contract is ERC20. IERC20Upgradeable ethPntContract = IERC20Upgradeable(ETHPNT_TOKEN_ADDRESS); // NOTE: First we need to know how much PNT this vault holds... @@ -337,14 +340,14 @@ contract Erc20Vault is if (_tokenAmount <= vaultPntTokenBalance) { // NOTE: If we can peg out entirely with PNT tokens, we do so... - pntContract.safeTransfer(_tokenRecipient, _tokenAmount); + pntContract.send(_tokenRecipient, _tokenAmount, _userData); } else if (vaultPntTokenBalance == 0) { // NOTE: Here we must peg out entirely with ETHPNT tokens instead... ethPntContract.safeTransfer(_tokenRecipient, _tokenAmount); } else { // NOTE: And so here we must peg out the total using as must PNT as possible, with // the remainder being sent as EthPNT... - pntContract.safeTransfer(_tokenRecipient, vaultPntTokenBalance); + pntContract.send(_tokenRecipient, vaultPntTokenBalance, _userData); ethPntContract.safeTransfer(_tokenRecipient, _tokenAmount - vaultPntTokenBalance); } } From f8e5c55714915ce6438b3ca50307dcc3fff8f3af Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 10:11:49 +0100 Subject: [PATCH 18/27] feat(contract & tests): <- rm setters for PNT & EthPNT addresses --- contracts/Erc20Vault.sol | 22 ---------------- test/change-ethpnt-address.test.js | 42 ------------------------------ test/change-pnt-address.test.js | 42 ------------------------------ 3 files changed, 106 deletions(-) delete mode 100644 test/change-ethpnt-address.test.js delete mode 100644 test/change-pnt-address.test.js diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index fba4007..732f64e 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -364,26 +364,4 @@ contract Erc20Vault is ORIGIN_CHAIN_ID = _newOriginChainId; return true; } - - function changePntTokenAddress( - address _newAddress - ) - public - onlyPNetwork - returns (bool success) - { - PNT_TOKEN_ADDRESS = _newAddress; - return true; - } - - function changeEthPntTokenAddress( - address _newAddress - ) - public - onlyPNetwork - returns (bool success) - { - ETHPNT_TOKEN_ADDRESS = _newAddress; - return true; - } } diff --git a/test/change-ethpnt-address.test.js b/test/change-ethpnt-address.test.js deleted file mode 100644 index 739b770..0000000 --- a/test/change-ethpnt-address.test.js +++ /dev/null @@ -1,42 +0,0 @@ -const { - getRandomEthAddress, - deployUpgradeableContract, -} = require('./test-utils') -const assert = require('assert') -const { ZERO_ADDRESS } = require('./test-constants') - -describe('Change EthPNT Address', () => { - const SAMPLE_PNT_ADDRESS = getRandomEthAddress(ethers) - const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' - const VAULT_PATH = 'contracts/Erc20Vault.sol:Erc20Vault' - - let NON_PNETWORK, VAULT_CONTRACT, NON_OWNED_VAULT_CONTRACT - - beforeEach(async () => { - const signers = await ethers.getSigners() - NON_PNETWORK = signers[1] - const dummyWethAddress = getRandomEthAddress(ethers) - VAULT_CONTRACT = await deployUpgradeableContract( - VAULT_PATH, - [ dummyWethAddress, [], SAMPLE_ORIGIN_CHAIN_ID ] - ) - NON_OWNED_VAULT_CONTRACT = VAULT_CONTRACT.connect(NON_PNETWORK) - }) - - it('pNetwork can change EthPNT address', async () => { - assert.strictEqual(await VAULT_CONTRACT.ETHPNT_TOKEN_ADDRESS(), ZERO_ADDRESS) - await VAULT_CONTRACT.changeEthPntTokenAddress(SAMPLE_PNT_ADDRESS) - assert.strictEqual(await VAULT_CONTRACT.ETHPNT_TOKEN_ADDRESS(), SAMPLE_PNT_ADDRESS) - }) - - it('Non pNetwork cannot change EthPNT address', async () => { - assert.strictEqual(await VAULT_CONTRACT.ETHPNT_TOKEN_ADDRESS(), ZERO_ADDRESS) - try { - await NON_OWNED_VAULT_CONTRACT.changeEthPntTokenAddress(SAMPLE_PNT_ADDRESS) - assert.fail('Should not have succeeded!') - } catch (_err) { - const expectedErr = 'Caller must be PNETWORK address!' - assert(_err.message.includes(expectedErr)) - } - }) -}) diff --git a/test/change-pnt-address.test.js b/test/change-pnt-address.test.js deleted file mode 100644 index 0ee110a..0000000 --- a/test/change-pnt-address.test.js +++ /dev/null @@ -1,42 +0,0 @@ -const { - getRandomEthAddress, - deployUpgradeableContract, -} = require('./test-utils') -const assert = require('assert') -const { ZERO_ADDRESS } = require('./test-constants') - -describe('Change PNT Address', () => { - const SAMPLE_PNT_ADDRESS = getRandomEthAddress(ethers) - const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' - const VAULT_PATH = 'contracts/Erc20Vault.sol:Erc20Vault' - - let NON_PNETWORK, VAULT_CONTRACT, NON_OWNED_VAULT_CONTRACT - - beforeEach(async () => { - const signers = await ethers.getSigners() - NON_PNETWORK = signers[1] - const dummyWethAddress = getRandomEthAddress(ethers) - VAULT_CONTRACT = await deployUpgradeableContract( - VAULT_PATH, - [ dummyWethAddress, [], SAMPLE_ORIGIN_CHAIN_ID ] - ) - NON_OWNED_VAULT_CONTRACT = VAULT_CONTRACT.connect(NON_PNETWORK) - }) - - it('pNetwork can change PNT address', async () => { - assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), ZERO_ADDRESS) - await VAULT_CONTRACT.changePntTokenAddress(SAMPLE_PNT_ADDRESS) - assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), SAMPLE_PNT_ADDRESS) - }) - - it('Non pNetwork cannot change PNT address', async () => { - assert.strictEqual(await VAULT_CONTRACT.PNT_TOKEN_ADDRESS(), ZERO_ADDRESS) - try { - await NON_OWNED_VAULT_CONTRACT.changePntTokenAddress(SAMPLE_PNT_ADDRESS) - assert.fail('Should not have succeeded!') - } catch (_err) { - const expectedErr = 'Caller must be PNETWORK address!' - assert(_err.message.includes(expectedErr)) - } - }) -}) From df4e6486c8daa702c9aabdbe15240b383913b978 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 10:29:17 +0100 Subject: [PATCH 19/27] chore(packages & linting): <- adds one of those to warn about skipped tests --- .eslintrc.js | 6 ++++-- package-lock.json | 16 ++++++++++++++++ package.json | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index db34925..8547190 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,10 +21,12 @@ module.exports = { }, plugins: [ 'no-only-tests', + 'no-skip-tests', ], rules: { - "no-only-tests/no-only-tests": "warn", - "max-len": ["error", 120, 2, { + 'no-skip-tests/no-skip-tests': 'warn', + 'no-only-tests/no-only-tests': 'warn', + 'max-len': ["error", 120, 2, { ignoreUrls: true, ignoreComments: false, ignoreRegExpLiterals: true, diff --git a/package-lock.json b/package-lock.json index 1adcb18..d97f8c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-mocha": "^5.3.0", "eslint-plugin-no-only-tests": "^3.0.0", + "eslint-plugin-no-skip-tests": "^1.1.0", "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", @@ -3359,6 +3360,15 @@ "node": ">=5.0.0" } }, + "node_modules/eslint-plugin-no-skip-tests": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-skip-tests/-/eslint-plugin-no-skip-tests-1.1.0.tgz", + "integrity": "sha512-o4Siv+8PqR8IilHnxdMogvZ2kAVpt3Zrz+LEQaWbremRn4cHcrrj+v+i4sr+ivQrsjz2NTxIRTTCEdYE90+ieQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint-plugin-node": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz", @@ -20426,6 +20436,12 @@ "integrity": "sha512-I0PeXMs1vu21ap45hey4HQCJRqpcoIvGcNTPJe+UhUm8TwjQ6//mCrDqF8q0WS6LgmRDwQ4ovQej0AQsAHb5yg==", "dev": true }, + "eslint-plugin-no-skip-tests": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-skip-tests/-/eslint-plugin-no-skip-tests-1.1.0.tgz", + "integrity": "sha512-o4Siv+8PqR8IilHnxdMogvZ2kAVpt3Zrz+LEQaWbremRn4cHcrrj+v+i4sr+ivQrsjz2NTxIRTTCEdYE90+ieQ==", + "dev": true + }, "eslint-plugin-node": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz", diff --git a/package.json b/package.json index 192bbf3..3e96e36 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-mocha": "^5.3.0", "eslint-plugin-no-only-tests": "^3.0.0", + "eslint-plugin-no-skip-tests": "^1.1.0", "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", From ac30a76384bf389997e23f7d91e1a61ed740449c Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 10:29:58 +0100 Subject: [PATCH 20/27] chore(tests): <- adds skips to those & note as to why --- test/pegging-in-ethpnt.test.js | 8 +++++++- test/pegging-out-pnt.test.js | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/pegging-in-ethpnt.test.js b/test/pegging-in-ethpnt.test.js index aaab5d3..a623ea4 100644 --- a/test/pegging-in-ethpnt.test.js +++ b/test/pegging-in-ethpnt.test.js @@ -11,7 +11,13 @@ const assert = require('assert') const { prop } = require('ramda') const { BigNumber } = require('ethers') -describe('Pegging In EthPNT Tests', () => { +// NOTE/FIXME This test is skipped because the PNT and EthPNT in the contract are hardcoded now. Initially +// they were editable by the pNetwork address, however this presented a possible attack surface. Instead, +// those addresses are now constants, (which in solidity do not take up a storage slot) since they're +// known ahead of time. However this now makes testing this behaviour difficult. One option is to do some +// trickery where we deploy two tokens, then re-write the contract (for tests only) on the fly, replacing +// the hardcoded constant addresses. +describe.skip('Pegging In EthPNT Tests', () => { const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' const VAULT_PATH = 'contracts/Erc20Vault.sol:Erc20Vault' diff --git a/test/pegging-out-pnt.test.js b/test/pegging-out-pnt.test.js index 9efb093..12c131c 100644 --- a/test/pegging-out-pnt.test.js +++ b/test/pegging-out-pnt.test.js @@ -7,7 +7,13 @@ const assert = require('assert') const { prop } = require('ramda') const { ADDRESS_PROP } = require('./test-constants') -describe('Pegging Out PNT Tests', () => { +// NOTE/FIXME This test is skipped because the PNT and EthPNT in the contract are hardcoded now. Initially +// they were editable by the pNetwork address, however this presented a possible attack surface. Instead, +// those addresses are now constants, (which in solidity do not take up a storage slot) since they're +// known ahead of time. However this now makes testing this behaviour difficult. One option is to do some +// trickery where we deploy two tokens, then re-write the contract (for tests only) on the fly, replacing +// the hardcoded constant addresses. +describe.skip('Pegging Out PNT Tests', () => { const VAULT_TOKEN_APPROVAL_AMOUNT = 1e6 const SAMPLE_ORIGIN_CHAIN_ID = '0x00000000' const VAULT_PATH = 'contracts/Erc20Vault.sol:Erc20Vault' From a7313ec37b87616427653e9dc9208ed5bc53379d Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 10:30:23 +0100 Subject: [PATCH 21/27] feat(contract): <- hardcode `PNT` and `EthPNT` addresses as constants in that --- contracts/Erc20Vault.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 732f64e..5ff38c4 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -30,8 +30,8 @@ contract Erc20Vault is IWETH public weth; // FIXME Ibid. bytes4 public ORIGIN_CHAIN_ID; address private wEthUnwrapperAddress; - address public PNT_TOKEN_ADDRESS; - address public ETHPNT_TOKEN_ADDRESS; + address public constant PNT_TOKEN_ADDRESS = 0x89Ab32156e46F46D02ade3FEcbe5Fc4243B9AAeD; + address public constant ETHPNT_TOKEN_ADDRESS = 0x8474a898677C3bc97f35A86c387aE34Bf272C860; event PegIn( address _tokenAddress, From a6e081d1a933548ec305ce73bc2fc5eb9e5d0f76 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 10:35:03 +0100 Subject: [PATCH 22/27] chore(contract): <- elucidate notes in that --- contracts/Erc20Vault.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 5ff38c4..e90b5b6 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -21,13 +21,13 @@ contract Erc20Vault is using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; IERC1820RegistryUpgradeable constant private _erc1820 = IERC1820RegistryUpgradeable( 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 - ); //FIXME The name of this should be `SCREAMING_SNAKE_CASE`. Will it break storage slot upgradeability? + ); bytes32 constant private TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); bytes32 constant private Erc777Token_INTERFACE_HASH = keccak256("ERC777Token"); - EnumerableSetUpgradeable.AddressSet private supportedTokens; // FIXME Ibid. + EnumerableSetUpgradeable.AddressSet private supportedTokens; address public PNETWORK; - IWETH public weth; // FIXME Ibid. + IWETH public weth; bytes4 public ORIGIN_CHAIN_ID; address private wEthUnwrapperAddress; address public constant PNT_TOKEN_ADDRESS = 0x89Ab32156e46F46D02ade3FEcbe5Fc4243B9AAeD; @@ -151,7 +151,7 @@ contract Erc20Vault is // NOTE: This is the special handling of the EthPNT token, where a peg in of EthPNT will // result in an event which will mint a PNT pToken on the other side of the bridge, thus - // merging the PNT & EthPNT tokens for all intents and purposes. + // making fungible the PNT & EthPNT tokens. address normalizedTokenAddress = _tokenAddress == ETHPNT_TOKEN_ADDRESS ? PNT_TOKEN_ADDRESS : _tokenAddress; @@ -339,13 +339,13 @@ contract Erc20Vault is uint256 vaultPntTokenBalance = pntContract.balanceOf(address(this)); if (_tokenAmount <= vaultPntTokenBalance) { - // NOTE: If we can peg out entirely with PNT tokens, we do so... + // NOTE: If we can peg out _entirely_ with PNT tokens, we do so... pntContract.send(_tokenRecipient, _tokenAmount, _userData); } else if (vaultPntTokenBalance == 0) { // NOTE: Here we must peg out entirely with ETHPNT tokens instead... ethPntContract.safeTransfer(_tokenRecipient, _tokenAmount); } else { - // NOTE: And so here we must peg out the total using as must PNT as possible, with + // NOTE: And so here we must peg out the total using as much PNT as possible, with // the remainder being sent as EthPNT... pntContract.send(_tokenRecipient, vaultPntTokenBalance, _userData); ethPntContract.safeTransfer(_tokenRecipient, _tokenAmount - vaultPntTokenBalance); From 085ccd31cfc255329854ed50521752ab3e5f178e Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 13:27:22 +0100 Subject: [PATCH 23/27] feat(existing-contracts): <- update eth mainnet one of that --- lib/show-existing-contract-addresses.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/show-existing-contract-addresses.js b/lib/show-existing-contract-addresses.js index 9008f4c..380bba9 100644 --- a/lib/show-existing-contract-addresses.js +++ b/lib/show-existing-contract-addresses.js @@ -9,7 +9,7 @@ const EXISTING_LOGIC_CONTRACT_ADDRESSES = [ { 'version': 'v2', 'chain': 'ropsten', 'address': '0x2Ea67a02058c3A0CE5f774949b6E8741B8D0a399' }, { 'version': 'v2', 'chain': 'rinkeby', 'address': '0x6819bbFdf803B8b87850916d3eEB3642DdE6C24F' }, { 'version': 'v2', 'chain': 'interim', 'address': '0xeEa7CE353a076898E35E82609e45918B5e4d0e0A' }, - { 'version': 'v2', 'chain': 'ethereum', 'address': '0xE01a9c36170b8Fa163C6a54D7aB3015C85e0186c' }, + { 'version': 'v2', 'chain': 'ethereum', 'address': '0xfbc347975C48578F4A25ECeEB61BC16356abE8a2' }, { 'version': 'v2', 'chain': 'goerli', 'address': '0xEa1FFBf0715FE7ccaae5d57dC698550f23581a27' }, { 'version': 'v2', 'chain': 'sepolia', 'address': '0x9fdbc63D5250Aa59cb6d5382eF4e92113fE1FC35' }, ] From ce8542929cad1b3c5fec4d8127c0ef0264111587 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 21:53:55 +0100 Subject: [PATCH 24/27] fix(vault): <- use correct `ethPNT` address in there --- contracts/Erc20Vault.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index e90b5b6..7b80801 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -31,7 +31,7 @@ contract Erc20Vault is bytes4 public ORIGIN_CHAIN_ID; address private wEthUnwrapperAddress; address public constant PNT_TOKEN_ADDRESS = 0x89Ab32156e46F46D02ade3FEcbe5Fc4243B9AAeD; - address public constant ETHPNT_TOKEN_ADDRESS = 0x8474a898677C3bc97f35A86c387aE34Bf272C860; + address public constant ETHPNT_TOKEN_ADDRESS = 0xf4eA6B892853413bD9d9f1a5D3a620A0ba39c5b2; event PegIn( address _tokenAddress, From 8766d8a46e213a38795c5eac77aa76762ed4bfb0 Mon Sep 17 00:00:00 2001 From: Greg Kapka Date: Wed, 28 Sep 2022 22:01:30 +0100 Subject: [PATCH 25/27] chore(package.json): <- bump minor version number in that --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e96e36..0025edd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ptokens-erc20-vault-smart-contract", - "version": "2.7.0", + "version": "2.8.0", "description": "The pToken ERC20 vault smart-contract & CLI", "main": "cli.js", "scripts": { From 91a71c6312c8e206a87769b8f93b4b9aa9acee0a Mon Sep 17 00:00:00 2001 From: gskapka Date: Tue, 16 May 2023 10:30:23 +0100 Subject: [PATCH 26/27] feat(vault): <- adds custom handling for GALA peg outs --- contracts/Erc20Vault.sol | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/contracts/Erc20Vault.sol b/contracts/Erc20Vault.sol index 7b80801..4f6efeb 100644 --- a/contracts/Erc20Vault.sol +++ b/contracts/Erc20Vault.sol @@ -1,4 +1,9 @@ // SPDX-License-Identifier: MIT + +// NOTE: This special version of the pTokens-erc20-vault is for ETH mainnet, and includes custom +// logic to handle ETHPNT<->PNT fungibility, as well as custom logic to handle GALA tokens after +// they upgraded from v1 to v2. + pragma solidity ^0.8.0; import "./wEth/IWETH.sol"; @@ -308,14 +313,21 @@ contract Erc20Vault is returns (bool success) { if (_tokenAddress == PNT_TOKEN_ADDRESS) { - handlePntPegOut(_tokenRecipient, _tokenAmount, _userData); - } else if (tokenIsErc777(_tokenAddress)) { + return handlePntPegOut(_tokenRecipient, _tokenAmount, _userData); + } + + if (_tokenAddress == 0x15D4c048F83bd7e37d49eA4C83a07267Ec4203dA) { // NOTE: Gala v1 + return handleGalaV1PegOut(_tokenRecipient, _tokenAmount); + } + + if (tokenIsErc777(_tokenAddress)) { // NOTE: This is an ERC777 token, so let's use its `send` function so that hooks are called... IERC777Upgradeable(_tokenAddress).send(_tokenRecipient, _tokenAmount, _userData); } else { // NOTE: Otherwise, we use standard ERC20 transfer function instead. IERC20Upgradeable(_tokenAddress).safeTransfer(_tokenRecipient, _tokenAmount); } + return true; } @@ -323,12 +335,31 @@ contract Erc20Vault is return _erc1820.getInterfaceImplementer(_tokenAddress, Erc777Token_INTERFACE_HASH) != address(0); } + function handleGalaV1PegOut( + address _tokenRecipient, + uint256 _tokenAmount + ) + internal + returns (bool success) + { + // NOTE: Neither Gala tokens implement hooks so we use a basic ERC20 transfer. + + IERC20Upgradeable(0x15D4c048F83bd7e37d49eA4C83a07267Ec4203dA) // NOTE Gala v1 + .safeTransfer(_tokenRecipient, _tokenAmount); + + IERC20Upgradeable(0xd1d2Eb1B1e90B638588728b4130137D262C87cae) // NOTE Gala v2 + .safeTransfer(_tokenRecipient, _tokenAmount); + + return true; + } + function handlePntPegOut( address _tokenRecipient, uint256 _tokenAmount, bytes memory _userData ) internal + returns (bool success) { // NOTE: The PNT contract is ERC777... IERC777Upgradeable pntContract = IERC777Upgradeable(PNT_TOKEN_ADDRESS); @@ -350,6 +381,8 @@ contract Erc20Vault is pntContract.send(_tokenRecipient, vaultPntTokenBalance, _userData); ethPntContract.safeTransfer(_tokenRecipient, _tokenAmount - vaultPntTokenBalance); } + + return true; } receive() external payable { } From ce5374afffa5bbd519c310a35e54d88e4d8ea61f Mon Sep 17 00:00:00 2001 From: gskapka Date: Wed, 17 May 2023 13:53:20 +0100 Subject: [PATCH 27/27] feat(scripts): <- adds one of those to show storage slot layout --- hardhat.config.js | 1 + package-lock.json | 56 ++++++++++++++++++++++++++++++++-- package.json | 4 ++- scripts/show-storage-layout.js | 7 +++++ 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 scripts/show-storage-layout.js diff --git a/hardhat.config.js b/hardhat.config.js index 0916d2f..6707276 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -9,6 +9,7 @@ const { const { assoc } = require('ramda') require('hardhat-erc1820') +require('hardhat-storage-layout') require('@nomiclabs/hardhat-waffle') require('@nomiclabs/hardhat-etherscan') require('@openzeppelin/hardhat-upgrades') diff --git a/package-lock.json b/package-lock.json index d97f8c3..712f0fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ptokens-erc20-vault-smart-contract", - "version": "2.7.0", + "version": "2.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ptokens-erc20-vault-smart-contract", - "version": "2.7.0", + "version": "2.8.0", "license": "MIT", "dependencies": { "@nomiclabs/hardhat-etherscan": "^3.1.0", @@ -37,6 +37,7 @@ "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", "ethereum-waffle": "^3.4.4", + "hardhat-storage-layout": "^0.1.7", "mocha": "^6.2.3" } }, @@ -2753,6 +2754,15 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/console-table-printer": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.11.1.tgz", + "integrity": "sha512-8LfFpbF/BczoxPwo2oltto5bph8bJkGOATXsg3E9ddMJOGnWJciKHldx2zDj5XIBflaKzPfVCjOTl6tMh7lErg==", + "dev": true, + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, "node_modules/cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -12505,6 +12515,18 @@ "hardhat": "^2.0.0" } }, + "node_modules/hardhat-storage-layout": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/hardhat-storage-layout/-/hardhat-storage-layout-0.1.7.tgz", + "integrity": "sha512-q723g2iQnJpRdMC6Y8fbh/stG6MLHKNxa5jq/ohjtD5znOlOzQ6ojYuInY8V4o4WcPyG3ty4hzHYunLf66/1+A==", + "dev": true, + "dependencies": { + "console-table-printer": "^2.9.0" + }, + "peerDependencies": { + "hardhat": "^2.0.3" + } + }, "node_modules/hardhat/node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -16783,6 +16805,12 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==", + "dev": true + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -19943,6 +19971,15 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "console-table-printer": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.11.1.tgz", + "integrity": "sha512-8LfFpbF/BczoxPwo2oltto5bph8bJkGOATXsg3E9ddMJOGnWJciKHldx2zDj5XIBflaKzPfVCjOTl6tMh7lErg==", + "dev": true, + "requires": { + "simple-wcswidth": "^1.0.1" + } + }, "cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -27595,6 +27632,15 @@ "integrity": "sha512-oQxe7Li8Ev6/Gs6PMcH9+IjaXS+xh6HyPBTGnlRVG4yfmkYF7ajVvzxfYY/FGlM9/j+F2uZjRhxsc//qisC82A==", "requires": {} }, + "hardhat-storage-layout": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/hardhat-storage-layout/-/hardhat-storage-layout-0.1.7.tgz", + "integrity": "sha512-q723g2iQnJpRdMC6Y8fbh/stG6MLHKNxa5jq/ohjtD5znOlOzQ6ojYuInY8V4o4WcPyG3ty4hzHYunLf66/1+A==", + "dev": true, + "requires": { + "console-table-printer": "^2.9.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -30361,6 +30407,12 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "simple-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==", + "dev": true + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index 0025edd..5108d95 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,11 @@ "description": "The pToken ERC20 vault smart-contract & CLI", "main": "cli.js", "scripts": { + "lint": "npx eslint .", "test": "npm run tests", "tests": "ENDPOINT=http://localhost:8545 npx hardhat test", "compile": "ENDPOINT=http://localhost:8545 npx hardhat compile", - "lint": "npx eslint ." + "showStorage": "ENDPOINT=http://localhost:8545 node ./scripts/show-storage-layout.js" }, "repository": { "type": "git", @@ -51,6 +52,7 @@ "eslint-plugin-promise": "^4.3.1", "eslint-plugin-standard": "^4.1.0", "ethereum-waffle": "^3.4.4", + "hardhat-storage-layout": "^0.1.7", "mocha": "^6.2.3" } } diff --git a/scripts/show-storage-layout.js b/scripts/show-storage-layout.js new file mode 100644 index 0000000..ec84116 --- /dev/null +++ b/scripts/show-storage-layout.js @@ -0,0 +1,7 @@ +const hre = require("hardhat"); + +async function main() { + console.log(await hre.storageLayout.export()) +} + +main()