forked from hyperledger-solang/solang
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Substrate: Implement
delegatecall()
(hyperledger-solang#1390)
- Loading branch information
Showing
12 changed files
with
509 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// Integration test against the delegatecall() function in combination with input forwarding and tail call flags. | ||
// WARNING: This code is neither EIP compliant nor secure nor audited nor intended to be used in production. | ||
|
||
// SPDX-License-Identifier: MIT | ||
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol) | ||
|
||
/** | ||
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM | ||
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to | ||
* be specified by overriding the virtual {_implementation} function. | ||
* | ||
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a | ||
* different contract through the {_delegate} function. | ||
* | ||
* The success and return data of the delegated call will be returned back to the caller of the proxy. | ||
*/ | ||
abstract contract Proxy { | ||
uint32 constant FORWARD_INPUT = 1; | ||
uint32 constant TAIL_CALL = 4; | ||
|
||
/** | ||
* @dev Delegates the current call to `implementation`. | ||
* | ||
* This function does not return to its internal call site. It will return directly to the external caller. | ||
*/ | ||
function _delegate(address implementation) internal virtual { | ||
implementation.delegatecall{flags: FORWARD_INPUT | TAIL_CALL}(hex""); | ||
} | ||
|
||
/** | ||
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback function | ||
* and {_fallback} should delegate. | ||
*/ | ||
function _implementation() internal view virtual returns (address); | ||
|
||
/** | ||
* @dev Delegates the current call to the address returned by `_implementation()`. | ||
* | ||
* This function does not return to its internal call site. It will return directly to the external caller. | ||
*/ | ||
function _fallback() internal virtual { | ||
_beforeFallback(); | ||
_delegate(_implementation()); | ||
} | ||
|
||
/** | ||
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. It will run if no other | ||
* function in the contract matches the call data. | ||
*/ | ||
fallback() external virtual { | ||
_fallback(); | ||
} | ||
|
||
/** | ||
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. It will run if call data | ||
* is empty. | ||
*/ | ||
receive() external payable virtual { | ||
_fallback(); | ||
} | ||
|
||
/** | ||
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` | ||
* call, or as part of the Solidity `fallback` or `receive` functions. | ||
* | ||
* If overridden should call `super._beforeFallback()`. | ||
*/ | ||
function _beforeFallback() internal virtual {} | ||
} | ||
|
||
// FIXME: This is NOT EIP-1967. | ||
// Have to mock it this way until issues #1387 and #1388 are resolved. | ||
abstract contract StorageSlot { | ||
mapping(bytes32 => address) getAddressSlot; | ||
} | ||
|
||
// Minimal proxy implementation; without security | ||
contract UpgradeableProxy is Proxy, StorageSlot { | ||
event Upgraded(address indexed implementation); | ||
|
||
bytes32 internal constant IMPLEMENTATION_SLOT = | ||
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; | ||
|
||
function _setImplementation(address newImplementation) private { | ||
// FIXME once issue #809 (supporting address.code) is solved | ||
// if (newImplementation.code.length == 0) { | ||
// revert ERC1967InvalidImplementation(newImplementation); | ||
// } | ||
// FIXME see #1387 and #1388 | ||
getAddressSlot[IMPLEMENTATION_SLOT] = newImplementation; | ||
} | ||
|
||
function upgradeTo(address newImplementation) public { | ||
_setImplementation(newImplementation); | ||
emit Upgraded(newImplementation); | ||
} | ||
|
||
function upgradeToAndCall( | ||
address newImplementation, | ||
bytes memory data | ||
) public returns (bytes ret) { | ||
upgradeTo(newImplementation); | ||
(bool ok, ret) = newImplementation.delegatecall(data); | ||
require(ok); | ||
} | ||
|
||
function _implementation() | ||
internal | ||
view | ||
virtual | ||
override | ||
returns (address) | ||
{ | ||
return getAddressSlot[IMPLEMENTATION_SLOT]; | ||
} | ||
} | ||
|
||
// Proxy implementation v1 | ||
contract UpgradeableImplV1 { | ||
uint public count; | ||
|
||
constructor() { | ||
count = 1; | ||
} | ||
|
||
function inc() external { | ||
count += 1; | ||
} | ||
} | ||
|
||
// Proxy implementation v2 | ||
contract UpgradeableImplV2 { | ||
uint public count; | ||
string public version; | ||
|
||
constructor() { | ||
version = "v2"; | ||
} | ||
|
||
function inc() external { | ||
count += 1; | ||
} | ||
|
||
function dec() external { | ||
count -= 1; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// https://solidity-by-example.org/delegatecall/ | ||
|
||
// SPDX-License-Identifier: MIT | ||
// pragma solidity ^0.8.17; | ||
|
||
// NOTE: Deploy this contract first | ||
contract Delegatee { | ||
// NOTE: storage layout must be the same as contract Delegator | ||
uint public num; | ||
address public sender; | ||
uint public value; | ||
|
||
function setVars(uint _num) public payable { | ||
num = _num; | ||
sender = msg.sender; | ||
value = msg.value; | ||
} | ||
} | ||
|
||
contract Delegator { | ||
uint public num; | ||
address public sender; | ||
uint public value; | ||
|
||
function setVars(address _contract, uint _num) public payable { | ||
// Delegatee's storage is set, Delegator is not modified. | ||
(bool success, bytes memory data) = _contract.delegatecall( | ||
abi.encodeWithSignature("setVars(uint256)", _num) | ||
); | ||
require(success); | ||
require(data.length == 0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import expect from 'expect'; | ||
import { weight, createConnection, deploy, transaction, aliceKeypair, daveKeypair, debug_buffer, query, } from './index'; | ||
import { ContractPromise } from '@polkadot/api-contract'; | ||
import { ApiPromise } from '@polkadot/api'; | ||
import { KeyringPair } from '@polkadot/keyring/types'; | ||
|
||
|
||
describe('Deploy the delegator and the delegatee contracts; test the delegatecall to work correct', () => { | ||
let conn: ApiPromise; | ||
let delegatee: ContractPromise; | ||
let delegator: ContractPromise; | ||
let alice: KeyringPair; | ||
let dave: KeyringPair; | ||
|
||
before(async function () { | ||
alice = aliceKeypair(); | ||
dave = daveKeypair(); | ||
conn = await createConnection(); | ||
|
||
const delegator_contract = await deploy(conn, alice, 'Delegator.contract', 0n); | ||
delegator = new ContractPromise(conn, delegator_contract.abi, delegator_contract.address); | ||
|
||
const delegatee_contract = await deploy(conn, alice, 'Delegatee.contract', 0n); | ||
delegatee = new ContractPromise(conn, delegatee_contract.abi, delegatee_contract.address); | ||
|
||
// Set delegatee storage to default values and alice address | ||
const gasLimit = await weight(conn, delegatee, 'setVars', [0n]); | ||
await transaction(delegatee.tx.setVars({ gasLimit }, [0n]), alice); | ||
}); | ||
|
||
after(async function () { | ||
await conn.disconnect(); | ||
}); | ||
|
||
it('Executes the delegatee in the context of the delegator', async function () { | ||
const value = 1000000n; | ||
const arg = 123456789n; | ||
const parameters = [delegatee.address, arg]; | ||
|
||
const gasLimit = await weight(conn, delegator, 'setVars', parameters); | ||
await transaction(delegator.tx.setVars({ gasLimit, value }, ...parameters), dave); | ||
|
||
// Storage of the delegatee must not change | ||
let num = await query(conn, alice, delegatee, "num"); | ||
expect(BigInt(num.output?.toString() ?? "")).toStrictEqual(0n); | ||
let balance = await query(conn, alice, delegatee, "value"); | ||
expect(BigInt(balance.output?.toString() ?? "")).toStrictEqual(0n); | ||
let sender = await query(conn, alice, delegatee, "sender"); | ||
expect(sender.output?.toJSON()).toStrictEqual(alice.address); | ||
|
||
// Storage of the delegator must have changed | ||
num = await query(conn, alice, delegator, "num"); | ||
expect(BigInt(num.output?.toString() ?? "")).toStrictEqual(arg); | ||
balance = await query(conn, alice, delegator, "value"); | ||
expect(BigInt(balance.output?.toString() ?? "")).toStrictEqual(value); | ||
sender = await query(conn, alice, delegator, "sender"); | ||
expect(sender.output?.toJSON()).toStrictEqual(dave.address); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import expect from 'expect'; | ||
import { weight, createConnection, deploy, transaction, aliceKeypair, query, } from './index'; | ||
import { ContractPromise } from '@polkadot/api-contract'; | ||
import { ApiPromise } from '@polkadot/api'; | ||
import { KeyringPair } from '@polkadot/keyring/types'; | ||
import { DecodedEvent } from '@polkadot/api-contract/types'; | ||
import { AccountId, ContractSelector } from '@polkadot/types/interfaces'; | ||
|
||
describe('Deploy the upgradable proxy and implementations; expect the upgrade mechanism to work', () => { | ||
// Helper: Upgrade implementation and execute a constructor that takes no arguments | ||
async function upgrade_and_constructor(impl: AccountId, constructor: ContractSelector) { | ||
const params = [impl, constructor]; | ||
const gasLimit = await weight(conn, proxy, 'upgradeToAndCall', params); | ||
let result: any = await transaction(proxy.tx.upgradeToAndCall({ gasLimit }, ...params), aliceKeypair()); | ||
|
||
let events: DecodedEvent[] = result.contractEvents; | ||
expect(events.length).toEqual(1); | ||
expect(events[0].event.identifier).toBe("Upgraded"); | ||
expect(events[0].args.map(a => a.toJSON())[0]).toEqual(params[0].toJSON()); | ||
} | ||
|
||
let conn: ApiPromise; | ||
let alice: KeyringPair; | ||
let proxy: ContractPromise; | ||
let counter: ContractPromise; | ||
|
||
before(async function () { | ||
alice = aliceKeypair(); | ||
conn = await createConnection(); | ||
|
||
const proxy_deployment = await deploy(conn, alice, 'UpgradeableProxy.contract', 0n); | ||
proxy = new ContractPromise(conn, proxy_deployment.abi, proxy_deployment.address); | ||
|
||
// Pretend the proxy contract to be implementation V1 | ||
const implV1 = await deploy(conn, alice, 'UpgradeableImplV1.contract', 0n); | ||
await upgrade_and_constructor(implV1.address, implV1.abi.constructors[0].selector); | ||
counter = new ContractPromise(conn, implV1.abi, proxy_deployment.address); | ||
const count = await query(conn, alice, counter, "count"); | ||
expect(BigInt(count.output?.toString() ?? "")).toStrictEqual(1n); | ||
}); | ||
|
||
after(async function () { | ||
await conn.disconnect(); | ||
}); | ||
|
||
it('Tests implementation and upgrading', async function () { | ||
// Test implementation V1 | ||
let gasLimit = await weight(conn, counter, 'inc', []); | ||
await transaction(counter.tx.inc({ gasLimit }), alice); | ||
await transaction(counter.tx.inc({ gasLimit }), alice); | ||
let count = await query(conn, alice, counter, "count"); | ||
expect(BigInt(count.output?.toString() ?? "")).toStrictEqual(3n); | ||
|
||
// Upgrade to implementation V2 | ||
const implV2 = await deploy(conn, alice, 'UpgradeableImplV2.contract', 0n); | ||
await upgrade_and_constructor(implV2.address, implV2.abi.constructors[0].selector); | ||
counter = new ContractPromise(conn, implV2.abi, proxy.address); | ||
|
||
// Test implementation V2 | ||
count = await query(conn, alice, counter, "count"); | ||
expect(BigInt(count.output?.toString() ?? "")).toStrictEqual(3n); | ||
|
||
gasLimit = await weight(conn, counter, 'dec', []); | ||
await transaction(counter.tx.dec({ gasLimit }), alice); | ||
count = await query(conn, alice, counter, "count"); | ||
expect(BigInt(count.output?.toString() ?? "")).toStrictEqual(2n); | ||
|
||
const version = await query(conn, alice, counter, "version"); | ||
expect(version.output?.toString()).toStrictEqual("v2"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.