Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contracts separation p.1 #103

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions contracts/ServiceRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//SPDX-License-Identifier: Unlicense
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try defaulting to external visibility where possible. see https://ethereum.stackexchange.com/questions/19380/external-vs-public-best-practices

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a pending PR on automation repo to change all ServiceRegistry visibility as well

pragma solidity >=0.7.6;
import "hardhat/console.sol";

contract ServiceRegistry {
mapping(bytes32 => uint256) public lastExecuted;
mapping(address => bool) private trustedAddresses;
mapping(bytes32 => address) private namedService;
address public owner;

uint256 public requiredDelay = 0; //big enaugh that any power of miner over timestamp does not matter

modifier validateInput(uint256 len) {
require(msg.data.length == len, "illegal-padding");
_;
}

modifier delayedExecution() {
bytes32 operationHash = keccak256(msg.data);
uint256 reqDelay = requiredDelay;

if (lastExecuted[operationHash] == 0 && reqDelay > 0) {
//not called before, scheduled for execution
// solhint-disable-next-line not-rely-on-time
lastExecuted[operationHash] = block.timestamp;
emit ChangeScheduled(
msg.data,
operationHash,
// solhint-disable-next-line not-rely-on-time
block.timestamp + reqDelay
);
} else {
require(
// solhint-disable-next-line not-rely-on-time
block.timestamp - reqDelay > lastExecuted[operationHash],
"delay-to-small"
);
// solhint-disable-next-line not-rely-on-time
emit ChangeApplied(msg.data, block.timestamp);
_;
lastExecuted[operationHash] = 0;
}
}

modifier onlyOwner() {
require(msg.sender == owner, "only-owner");
_;
}

constructor(uint256 initialDelay) {
require(initialDelay < type(uint256).max,"risk-of-overflow");
requiredDelay = initialDelay;
owner = msg.sender;
}

function transferOwnership(address newOwner)
public
onlyOwner
validateInput(36)
delayedExecution
{
owner = newOwner;
}

function changeRequiredDelay(uint256 newDelay)
public
onlyOwner
validateInput(36)
delayedExecution
{
requiredDelay = newDelay;
}

function addTrustedAddress(address trustedAddress)
public
onlyOwner
validateInput(36)
delayedExecution
{
trustedAddresses[trustedAddress] = true;
}

function removeTrustedAddress(address trustedAddress)
public
onlyOwner
validateInput(36)
{
trustedAddresses[trustedAddress] = false;
}

function isTrusted(address testedAddress) public view returns (bool) {
return trustedAddresses[testedAddress];
}

function getServiceNameHash(string memory name)
public
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(name));
}

function addNamedService(bytes32 serviceNameHash, address serviceAddress)
public
onlyOwner
validateInput(68)
delayedExecution
{
require(
namedService[serviceNameHash] == address(0),
"service-override"
);
namedService[serviceNameHash] = serviceAddress;
}

function updateNamedService(bytes32 serviceNameHash, address serviceAddress)
public
onlyOwner
validateInput(68)
delayedExecution
{
require(
namedService[serviceNameHash] != address(0),
"service-does-not-exist"
);
namedService[serviceNameHash] = serviceAddress;
}

function removeNamedService(bytes32 serviceNameHash)
public
onlyOwner
validateInput(36)
{
require(
namedService[serviceNameHash] != address(0),
"service-does-not-exist"
);
namedService[serviceNameHash] = address(0);
emit RemoveApplied(serviceNameHash);
}

function getRegisteredService(string memory serviceName)
public
view
returns (address)
{
address retVal = getServiceAddress(
keccak256(abi.encodePacked(serviceName))
);
return retVal;
}

function getServiceAddress(bytes32 serviceNameHash)
public
view
returns (address)
{
return namedService[serviceNameHash];
}

function clearScheduledExecution(bytes32 scheduledExecution)
public
onlyOwner
validateInput(36)
{
require(lastExecuted[scheduledExecution] > 0, "execution-not-sheduled");
lastExecuted[scheduledExecution] = 0;
emit ChangeCancelled(scheduledExecution);
}

event ChangeScheduled(
bytes data,
bytes32 dataHash,
uint256 firstPossibleExecutionTime
);
event ChangeCancelled(bytes32 data);
event ChangeApplied(bytes data, uint256 firstPossibleExecutionTime);
event RemoveApplied(bytes32 nameHash);
}
14 changes: 14 additions & 0 deletions contracts/actions/ActionBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.7.6;
pragma abicoder v2;

abstract contract ActionBase {
enum ActionType {
FLASHLOAN,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what You tried to do with this,
I think there ate two kinds of operations

  1. with callbacks
  2. without callbacks

but maybe there is really only one, and fact that operaation makes a callback is its internal detail as long as valle address is passed as parameter

DEFAULT
}

function executeAction(bytes[] memory _callData) public payable virtual returns (bytes32);

function actionType() public pure virtual returns (uint8);
}
58 changes: 58 additions & 0 deletions contracts/actions/OperationRunner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.7.6;
pragma abicoder v2;

import "../interfaces/mcd/IJoin.sol";
import "../interfaces/mcd/IManager.sol";
import "./ActionBase.sol";
import "hardhat/console.sol";
import "../ServiceRegistry.sol";

struct Operation {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in 0.8+ solc and in 0.7 with abie encoder v2 pragma constructs like:

pragma solidity ^0.7.6;
pragma abicoder v2;

struct Action{
    bytes32 id;
    bytes data;
}
struct Operation{
    Action[] actions;

}

contract Silly {
    function execute(Operation calldata op) public returns (bool){

    }
}

are possible

string name;
bytes[][] callData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why it is multidimentional? I'm quite sure we can get away with one dimention and then do abi.decode
similar trick like:

) = abi.decode(params, (uint256, ExchangeData, CdpData, GuniAddressRegistry));

generally it is reasonable to assume that action knows format of its parameters, so You pass bytes to action and it decodes it in any way it wants

bytes32[] actionIds;
address serviceRegistryAddr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serviceRegistryAddress can be immutable instead I guess it will be allways the same

}

contract OperationRunner {
function executeOperation(Operation memory operation) public payable {
_executeActions(operation);
}

function _executeActions(Operation memory operation) internal {
// address firstActionAddr = ServiceRegistry(operation.serviceRegistryAddr)
// .getServiceAddress(operation.actionIds[0]);

bytes32[] memory returnValues = new bytes32[](operation.actionIds.length);

// if (isFlashLoanAction(firstActionAddr)) {
// executeFlashloan(operation, firstActionAddr, returnValues);
// } else {
for (uint256 i = 0; i < operation.actionIds.length; ++i) {
returnValues[i] = _executeAction(operation, i, returnValues);
// _executeAction(operation, i);
}
// }
}

function _executeAction(
Operation memory operation,
uint256 _index,
bytes32[] memory returnValues
) internal returns (bytes32 response) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we are about to support return values, i think it should be bytes not bytes32

address actionAddress = ServiceRegistry(operation.serviceRegistryAddr).getServiceAddress(
operation.actionIds[_index]
);

actionAddress.delegatecall(
abi.encodeWithSignature("executeAction(bytes[])", operation.callData[_index])
);

return "";
}

function isFlashLoanAction(address actionAddr) internal pure returns (bool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it used after all?

return ActionBase(actionAddr).actionType() == uint8(ActionBase.ActionType.FLASHLOAN);
}
}
50 changes: 50 additions & 0 deletions contracts/actions/deposit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.7.6;
pragma abicoder v2;

import "../interfaces/mcd/IJoin.sol";
import "../interfaces/mcd/IManager.sol";
import "./ActionBase.sol";
import "hardhat/console.sol";

contract Deposit is ActionBase {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a right name ? looks like open empty vault to me

function executeAction(bytes[] memory _callData)
public
payable
override
returns (
// ) public payable virtual override returns (bytes32) {
bytes32
)
{
(address joinAddr, address mcdManager) = parseInputs(_callData);
// address joinAddr = 0x2F0b23f53734252Bda2277357e97e1517d6B042A;
// address mcdManager = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39;

// // joinAddr = _parseParamAddr(joinAddr, _paramMapping[0], _subData, _returnValues);

console.log("address this", address(this));

uint256 newVaultId = _mcdOpen(joinAddr, mcdManager);

return bytes32(newVaultId);
}

function actionType() public pure override returns (uint8) {
return uint8(ActionType.DEFAULT);
}

function _mcdOpen(address _joinAddr, address _mcdManager) internal returns (uint256 vaultId) {
bytes32 ilk = IJoin(_joinAddr).ilk();
vaultId = IManager(_mcdManager).open(ilk, address(this));
}

function parseInputs(bytes[] memory _callData)
internal
pure
returns (address joinAddr, address mcdManager)
{
joinAddr = abi.decode(_callData[0], (address));
mcdManager = abi.decode(_callData[1], (address));
}
}
26 changes: 26 additions & 0 deletions contracts/actions/flashloan.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.7.6;
pragma abicoder v2;

import "../interfaces/mcd/IJoin.sol";
import "../interfaces/mcd/IManager.sol";
import "./ActionBase.sol";
import "hardhat/console.sol";

contract Flashloan is ActionBase {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is just placeholder,

flashloan can be (I think ) just and Operation like any other, it takes some some parameters that describes flash loan and then another operation which it executes on a runner.

function actionType() public pure override returns (uint8) {
return uint8(ActionType.FLASHLOAN);
}

function executeAction(bytes[] memory _callData)
public
payable
override
returns (
// ) public payable virtual override returns (bytes32) {
bytes32
)
{
return "";
}
}
39 changes: 39 additions & 0 deletions contracts/actions/openVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity >=0.7.6;
pragma abicoder v2;

import "../interfaces/mcd/IJoin.sol";
import "../interfaces/mcd/IManager.sol";
import "./ActionBase.sol";
import "hardhat/console.sol";

contract OpenVault is ActionBase {
function executeAction(bytes[] memory _callData) public payable override returns (bytes32) {
// (address joinAddr, address mcdManager) = parseInputs(_callData);

address joinAddr = 0x2F0b23f53734252Bda2277357e97e1517d6B042A;
address mcdManager = 0x5ef30b9986345249bc32d8928B7ee64DE9435E39;

uint256 newVaultId = _mcdOpen(joinAddr, mcdManager);

return bytes32(newVaultId);
}

function actionType() public pure override returns (uint8) {
return uint8(ActionType.DEFAULT);
}

function _mcdOpen(address _joinAddr, address _mcdManager) internal returns (uint256 vaultId) {
bytes32 ilk = IJoin(_joinAddr).ilk();
vaultId = IManager(_mcdManager).open(ilk, address(this));
}

function parseInputs(bytes[] memory _callData)
internal
pure
returns (address joinAddr, address mcdManager)
{
joinAddr = abi.decode(_callData[0], (address));
mcdManager = abi.decode(_callData[1], (address));
}
}
Loading