97 lines
4.4 KiB
Solidity
97 lines
4.4 KiB
Solidity
pragma solidity 0.4.24;
|
|
|
|
/**
|
|
* @title Proxy
|
|
* @dev Gives the possibility to delegate any call to a foreign implementation.
|
|
*/
|
|
contract Proxy {
|
|
/**
|
|
* @dev Tells the address of the implementation where every call will be delegated.
|
|
* @return address of the implementation to which it will be delegated
|
|
*/
|
|
/* solcov ignore next */
|
|
function implementation() public view returns (address);
|
|
|
|
/**
|
|
* @dev Fallback function allowing to perform a delegatecall to the given implementation.
|
|
* This function will return whatever the implementation call returns
|
|
*/
|
|
function() public payable {
|
|
// solhint-disable-previous-line no-complex-fallback
|
|
address _impl = implementation();
|
|
require(_impl != address(0));
|
|
assembly {
|
|
/*
|
|
0x40 is the "free memory slot", meaning a pointer to next slot of empty memory. mload(0x40)
|
|
loads the data in the free memory slot, so `ptr` is a pointer to the next slot of empty
|
|
memory. It's needed because we're going to write the return data of delegatecall to the
|
|
free memory slot.
|
|
*/
|
|
let ptr := mload(0x40)
|
|
/*
|
|
`calldatacopy` is copy calldatasize bytes from calldata
|
|
First argument is the destination to which data is copied(ptr)
|
|
Second argument specifies the start position of the copied data.
|
|
Since calldata is sort of its own unique location in memory,
|
|
0 doesn't refer to 0 in memory or 0 in storage - it just refers to the zeroth byte of calldata.
|
|
That's always going to be the zeroth byte of the function selector.
|
|
Third argument, calldatasize, specifies how much data will be copied.
|
|
calldata is naturally calldatasize bytes long (same thing as msg.data.length)
|
|
*/
|
|
calldatacopy(ptr, 0, calldatasize)
|
|
/*
|
|
delegatecall params explained:
|
|
gas: the amount of gas to provide for the call. `gas` is an Opcode that gives
|
|
us the amount of gas still available to execution
|
|
|
|
_impl: address of the contract to delegate to
|
|
|
|
ptr: to pass copied data
|
|
|
|
calldatasize: loads the size of `bytes memory data`, same as msg.data.length
|
|
|
|
0, 0: These are for the `out` and `outsize` params. Because the output could be dynamic,
|
|
these are set to 0, 0 so the output data will not be written to memory. The output
|
|
data will be read using `returndatasize` and `returdatacopy` instead.
|
|
|
|
result: This will be 0 if the call fails and 1 if it succeeds
|
|
*/
|
|
let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
|
|
/*
|
|
|
|
*/
|
|
/*
|
|
ptr current points to the value stored at 0x40,
|
|
because we assigned it like ptr := mload(0x40).
|
|
Because we use 0x40 as a free memory pointer,
|
|
we want to make sure that the next time we want to allocate memory,
|
|
we aren't overwriting anything important.
|
|
So, by adding ptr and returndatasize,
|
|
we get a memory location beyond the end of the data we will be copying to ptr.
|
|
We place this in at 0x40, and any reads from 0x40 will now read from free memory
|
|
*/
|
|
mstore(0x40, add(ptr, returndatasize))
|
|
/*
|
|
`returndatacopy` is an Opcode that copies the last return data to a slot. `ptr` is the
|
|
slot it will copy to, 0 means copy from the beginning of the return data, and size is
|
|
the amount of data to copy.
|
|
`returndatasize` is an Opcode that gives us the size of the last return data. In this case, that is the size of the data returned from delegatecall
|
|
*/
|
|
returndatacopy(ptr, 0, returndatasize)
|
|
|
|
/*
|
|
if `result` is 0, revert.
|
|
if `result` is 1, return `size` amount of data from `ptr`. This is the data that was
|
|
copied to `ptr` from the delegatecall return data
|
|
*/
|
|
switch result
|
|
case 0 {
|
|
revert(ptr, returndatasize)
|
|
}
|
|
default {
|
|
return(ptr, returndatasize)
|
|
}
|
|
}
|
|
}
|
|
}
|