체인의정석

delegateCall에서의 상태변화 가능여부 및 사용하면 안되는 이유 본문

블록체인/Solidity

delegateCall에서의 상태변화 가능여부 및 사용하면 안되는 이유

체인의정석 2022. 7. 8. 19:06
728x90
반응형

delegateCall에 대한 함수를 작성했는데 hardhat에서는 테스트 코드가 오류가 나는 상황

 

문제가 없는거 같아 같은 코드를 remix에서 작성했는데 에러가 발생하지 않았다.

알 수 없는 오류가 발생했으니 하드햇에 보고라는 메세지가 떴다.

=> Hardhat의 경우 아직 delegateCall을 제대로 테스트 할 경우 오류가 날 수 있으므로 주의

 

TestDelegate 컨트렉트

pragma solidity >=0.7.0 <0.9.0;

contract TestDelegate {

bool public check;

  function testDelegateCall(uint256 _value) public returns(uint256) {
    check=true;
    return _value;
  }
}

TestDelegateCaller 컨트렉트

pragma solidity >=0.7.0 <0.9.0;
// import "@openzeppelin/contracts/access/Ownable.sol";

contract TestDelegateCaller {
   uint256 public result;
   bool public checked;

    function doDelegateCall(address _delegate, uint256 _value) public returns(uint256) {
    (bool check, bytes memory data) = address(_delegate).delegatecall(abi.encodeWithSignature("testDelegateCall(uint256)",_value));
    (uint256 tokenId) = abi.decode(data, (uint256));
    result = tokenId;
    checked = check;
    return tokenId;
  }
}

 

테스트 코드

import { expect } from "chai";
import { ethers } from "hardhat";
import { Contract } from "ethers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";

// let exchange: Contract;
let DelegateCall: Contract;
let DelegateCaller: Contract;

let signerOwner: SignerWithAddress;

describe("Start Delegate Call test", async function () {
  it("initialize signer", async function () {
    const signers = await ethers.getSigners();
    signerOwner = signers[0];
    console.log("signerOwner", signerOwner.address);
  });

  it("should deploy DelegateCall contract correctly", async function () {
    const DelegateCallFactory = await ethers.getContractFactory("TestDelegate");
    DelegateCall = await DelegateCallFactory.deploy();
    await DelegateCall.deployed();
  });

  it("should deploy DelegateCaller contract correctly", async function () {
    const DelegateCallerFactory = await ethers.getContractFactory(
      "TestDelegateCaller"
    );
    DelegateCaller = await DelegateCallerFactory.deploy();
    await DelegateCaller.deployed();
  });

  it("do delegate call testing", async function () {
    const delegateCallTx = await DelegateCaller.doDelegateCall(
      DelegateCaller.address,
      1
    );
    const result = await delegateCallTx.wait();
    console.log(result);
    expect(await DelegateCaller.result()).to.equal(1);
    await expect(DelegateCaller.checked()).to.be.true;
  });
});

테스트로 배포한 코드 1이 delegate 함수 호출하는 delegateCaller 컨트렉트

https://rinkeby.etherscan.io/address/0x9f9de9156d39aabc758b47f6e038724b8152f15e#code

 

TestDelegateCaller | Address 0x9f9de9156d39aabc758b47f6e038724b8152f15e | Etherscan

The Contract Address 0x9f9de9156d39aabc758b47f6e038724b8152f15e page allows users to view the source code, transactions, balances, and analytics for the contract address. Users can also interact and make transactions to the contract directly on Etherscan.

rinkeby.etherscan.io

테스트로 배포한 코드 2가 호출 되는 delegate 함수가 있는 testDelegate 컨트렉트

https://rinkeby.etherscan.io/address/0x7c1cd789f73d8a58227c640082c7ce87f8f5bc06#code

 

TestDelegate | Address 0x7c1cd789f73d8a58227c640082c7ce87f8f5bc06 | Etherscan

The Contract Address 0x7c1cd789f73d8a58227c640082c7ce87f8f5bc06 page allows users to view the source code, transactions, balances, and analytics for the contract address. Users can also interact and make transactions to the contract directly on Etherscan.

rinkeby.etherscan.io

이더스캔에서는 Delegatecall 실행을 막아놔서 Remix에서 실행해 보았다.

https://rinkeby.etherscan.io/address/0xb0c10422be9a962a1dd4ec5d01a2667db7de4d1f

 

Address 0xb0c10422be9a962a1dd4ec5d01a2667db7de4d1f | Etherscan

The Address 0xb0c10422be9a962a1dd4ec5d01a2667db7de4d1f page allows users to view transactions, balances, token holdings and transfers of ERC-20, ERC-721 and ERC-1155 (NFT) tokens, and analytics.

rinkeby.etherscan.io

확인 결과 인터널 트랜잭션도 다 찍혔고 바뀐 결과값이 리턴되었지만

Call의 대상이 되는 컨트렉트에서 찍히는 값은 바뀜이 없었다.

 

=> 결론

delegateCall을 통해 함수를 호출할 경우 에러가 나지는 않고 값을 리턴 받을 경우 상태변화는 일어나지 않는다.

 

여기에 대해 학회장님에게 피드백을 받았는데

 

delegateCall이 슬롯을 변경시켜서 메모리 영역에서 계산하고 끝나는게 아니고 영구적으로 바뀌는 부분때문에 외부인한테 노출이 안되어도 자체적으로 보안에 취약해진다고 한다.

delegateCall의 경우 다른 컨트렉트에서 로직을 가져와서 실행시킨 컨트렉트에서 실행하는거라 상태변화가 안되는게 맞다고 확인을 받았다.

 

이걸 안정적으로 시켜주는 부분이 있는데 찾아보려면 다이아몬드 스토리지나 UUIP를 알아봐야 된다고 한다.

 

728x90
반응형
Comments