Skip to content

Commit

Permalink
feat: add ITS utils (#84)
Browse files Browse the repository at this point in the history
* Add an implementation contract that can protect it's function unleass proxied.

* Added a multicall file.

* Added a contract that can prevent re-entrancy

* feat(utils): more utils

* test(utils): most utils coverage

* style(utils): prettier

* test(utils): more utils coverage

* refactor(Implementation): dependencies

* style(solidity): prettier

* refactor(utils): naming convention and tests

* refactor(Paused): param name

* Update test/utils/ReentrancyGuard.js

* move setup to IImplementation

---------

Co-authored-by: Kiryl Yermakou <[email protected]>
Co-authored-by: Milap Sheth <[email protected]>
  • Loading branch information
3 people authored Oct 19, 2023
1 parent 9ff8488 commit b94ea74
Show file tree
Hide file tree
Showing 25 changed files with 651 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,4 @@ dist
# Build
artifacts
/interfaces
temp-arguments.js
6 changes: 2 additions & 4 deletions contracts/interfaces/IAxelarGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
pragma solidity ^0.8.0;

import { IGovernable } from './IGovernable.sol';
import { IImplementation } from './IImplementation.sol';

interface IAxelarGateway is IGovernable {
interface IAxelarGateway is IImplementation, IGovernable {
/**********\
|* Errors *|
\**********/

error NotSelf();
error NotProxy();
error InvalidCodeHash();
error SetupFailed();
error InvalidAuthModule();
Expand Down Expand Up @@ -194,7 +194,5 @@ interface IAxelarGateway is IGovernable {
|* External Functions *|
\**********************/

function setup(bytes calldata params) external;

function execute(bytes calldata input) external;
}
11 changes: 11 additions & 0 deletions contracts/interfaces/IImplementation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IContractIdentifier } from './IContractIdentifier.sol';

interface IImplementation is IContractIdentifier {
error NotProxy();

function setup(bytes calldata data) external;
}
21 changes: 21 additions & 0 deletions contracts/interfaces/IMulticall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @title IMulticall
* @notice This contract is a multi-functional smart contract which allows for multiple
* contract calls in a single transaction.
*/
interface IMulticall {
error MulticallFailed();

/**
* @notice Performs multiple delegate calls and returns the results of all calls as an array
* @dev This function requires that the contract has sufficient balance for the delegate calls.
* If any of the calls fail, the function will revert with the failure message.
* @param data An array of encoded function calls
* @return results An bytes array with the return data of each function call
*/
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
}
22 changes: 22 additions & 0 deletions contracts/interfaces/IPausable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @title Pausable
* @notice This contract provides a mechanism to halt the execution of specific functions
* if a pause condition is activated.
*/
interface IPausable {
event Paused(address indexed account);
event Unpaused(address indexed account);

error Pause();
error NotPaused();

/**
* @notice Check if the contract is paused
* @return paused A boolean representing the pause status. True if paused, false otherwise.
*/
function paused() external view returns (bool);
}
12 changes: 12 additions & 0 deletions contracts/interfaces/IReentrancyGuard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @title ReentrancyGuard
* @notice This contract provides a mechanism to halt the execution of specific functions
* if a pause condition is activated.
*/
interface IReentrancyGuard {
error ReentrantCall();
}
7 changes: 2 additions & 5 deletions contracts/interfaces/IUpgradable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
pragma solidity ^0.8.0;

import { IOwnable } from './IOwnable.sol';
import { IContractIdentifier } from './IContractIdentifier.sol';
import { IImplementation } from './IImplementation.sol';

// General interface for upgradable contracts
interface IUpgradable is IOwnable, IContractIdentifier {
interface IUpgradable is IOwnable, IImplementation {
error InvalidCodeHash();
error InvalidImplementation();
error SetupFailed();
error NotProxy();

event Upgraded(address indexed newImplementation);

Expand All @@ -21,6 +20,4 @@ interface IUpgradable is IOwnable, IContractIdentifier {
bytes32 newImplementationCodeHash,
bytes calldata params
) external;

function setup(bytes calldata data) external;
}
38 changes: 38 additions & 0 deletions contracts/libs/AddressBytes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @title AddressBytesUtils
* @dev This library provides utility functions to convert between `address` and `bytes`.
*/
library AddressBytes {
error InvalidBytesLength(bytes bytesAddress);

/**
* @dev Converts a bytes address to an address type.
* @param bytesAddress The bytes representation of an address
* @return addr The converted address
*/
function toAddress(bytes memory bytesAddress) internal pure returns (address addr) {
if (bytesAddress.length != 20) revert InvalidBytesLength(bytesAddress);

assembly {
addr := mload(add(bytesAddress, 20))
}
}

/**
* @dev Converts an address to bytes.
* @param addr The address to be converted
* @return bytesAddress The bytes representation of the address
*/
function toBytes(address addr) internal pure returns (bytes memory bytesAddress) {
bytesAddress = new bytes(20);
// we can test if using a single 32 byte variable that is the address with the length together and using one mstore would be slightly cheaper.
assembly {
mstore(add(bytesAddress, 20), addr)
mstore(bytesAddress, 20)
}
}
}
18 changes: 18 additions & 0 deletions contracts/test/libs/TestAddressBytes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { AddressBytes } from '../../libs/AddressBytes.sol';

contract TestAddressBytes {
using AddressBytes for address;
using AddressBytes for bytes;

function toAddress(bytes memory bytesAddress) external pure returns (address addr) {
return bytesAddress.toAddress();
}

function toBytes(address addr) external pure returns (bytes memory bytesAddress) {
return addr.toBytes();
}
}
4 changes: 4 additions & 0 deletions contracts/test/mocks/MockGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -571,4 +571,8 @@ contract MockGateway is IAxelarGateway {
bytes32 newImplementationCodeHash,
bytes calldata setupParams
) external override {}

function contractId() external pure override returns (bytes32) {
return keccak256('MockGateway');
}
}
17 changes: 17 additions & 0 deletions contracts/test/upgradable/TestImplementation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { Implementation } from '../../upgradable/Implementation.sol';

contract TestImplementation is Implementation {
uint256 public val;

function setup(bytes calldata params) external override onlyProxy {
val = abi.decode(params, (uint256));
}

function contractId() external pure override returns (bytes32) {
return keccak256('TestImplementation');
}
}
32 changes: 32 additions & 0 deletions contracts/test/utils/TestMulticall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { Multicall } from '../../utils/Multicall.sol';

contract TestMulticall is Multicall {
uint256 public nonce;
bytes[] public lastMulticallReturns;
event Function1Called(uint256 nonce_);
event Function2Called(uint256 nonce_);

function function1() external returns (uint256) {
uint256 nonce_ = nonce++;
emit Function1Called(nonce_);
return nonce_;
}

function function2() external returns (uint256) {
uint256 nonce_ = nonce++;
emit Function2Called(nonce_);
return nonce_;
}

function multicallTest(bytes[] calldata data) external {
lastMulticallReturns = multicall(data);
}

function getLastMulticallReturns() external view returns (bytes[] memory r) {
return lastMulticallReturns;
}
}
21 changes: 21 additions & 0 deletions contracts/test/utils/TestPausable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { Pausable } from '../../utils/Pausable.sol';

contract TestPausable is Pausable {
event TestEvent();

function pause() external {
_pause();
}

function unpause() external {
_unpause();
}

function testPaused() external whenNotPaused {
emit TestEvent();
}
}
21 changes: 21 additions & 0 deletions contracts/test/utils/TestReentrancyGuard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { ReentrancyGuard } from '../../utils/ReentrancyGuard.sol';

contract TestReentrancyGuard is ReentrancyGuard {
uint256 public value;

constructor() {
require(ENTERED_SLOT == uint256(keccak256('ReentrancyGuard:entered')) - 1, 'invalid constant');
}

function testFunction() external noReEntrancy {
value = 1;
this.callback();
value = 2;
}

function callback() external noReEntrancy {}
}
38 changes: 38 additions & 0 deletions contracts/upgradable/Implementation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IImplementation } from '../interfaces/IImplementation.sol';

/**
* @title Implementation
* @notice This contract serves as a base for other contracts and enforces a proxy-first access restriction.
* @dev Derived contracts must implement the setup function.
*/
abstract contract Implementation is IImplementation {
address private immutable implementationAddress;

/**
* @dev Contract constructor that sets the implementation address to the address of this contract.
*/
constructor() {
implementationAddress = address(this);
}

/**
* @dev Modifier to require the caller to be the proxy contract.
* Reverts if the caller is the current contract (i.e., the implementation contract itself).
*/
modifier onlyProxy() {
if (implementationAddress == address(this)) revert NotProxy();
_;
}

/**
* @notice Initializes contract parameters.
* This function is intended to be overridden by derived contracts.
* The overriding function must have the onlyProxy modifier.
* @param params The parameters to be used for initialization
*/
function setup(bytes calldata params) external virtual;
}
20 changes: 5 additions & 15 deletions contracts/upgradable/Upgradable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

pragma solidity ^0.8.0;

import { IImplementation } from '../interfaces/IImplementation.sol';
import { IUpgradable } from '../interfaces/IUpgradable.sol';
import { Ownable } from '../utils/Ownable.sol';
import { Implementation } from './Implementation.sol';

/**
* @title Upgradable Contract
* @notice This contract provides an interface for upgradable smart contracts and includes the functionality to perform upgrades.
*/
abstract contract Upgradable is Ownable, IUpgradable {
abstract contract Upgradable is Ownable, Implementation, IUpgradable {
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
address internal immutable implementationAddress;

/**
* @notice Constructor sets the implementation address to the address of the contract itself
Expand All @@ -21,18 +22,7 @@ abstract contract Upgradable is Ownable, IUpgradable {
* @dev The owner is initially set as address(1) because the actual owner is set within the proxy. It is not
* set as the zero address because Ownable is designed to throw an error for ownership transfers to the zero address.
*/
constructor() Ownable(address(1)) {
implementationAddress = address(this);
}

/**
* @notice Modifier to ensure that a function can only be called by the proxy
*/
modifier onlyProxy() {
// Prevent setup from being called on the implementation
if (address(this) == implementationAddress) revert NotProxy();
_;
}
constructor() Ownable(address(1)) {}

/**
* @notice Returns the address of the current implementation
Expand Down Expand Up @@ -80,7 +70,7 @@ abstract contract Upgradable is Ownable, IUpgradable {
* @param data Initialization data for the contract
* @dev This function is only callable by the proxy contract.
*/
function setup(bytes calldata data) external override onlyProxy {
function setup(bytes calldata data) external override(IImplementation, Implementation) onlyProxy {
_setup(data);
}

Expand Down
Loading

0 comments on commit b94ea74

Please sign in to comment.